unit PXT.Types;
(*
 * Copyright (c) 2015 - 2025 Dr. Yuriy Kotsarenko. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and limitations under the License.
 *)
{< Essential types, constants and functions working with vectors, colors, pixels and rectangles that are
   used throughout the framework. }
interface

{$INCLUDE PXT.Config.inc}
{$TYPEINFO ON}

uses
  Math, Generics.Collections;

{$REGION 'Basic Types'}

type
{$IFDEF DELPHI_NO_UTF8STRING}
  Utf8String = AnsiString;
{$ENDIF}

{$IFNDEF FPC}
  // Pointer type represented as a signed integer.
  PtrInt = NativeInt;

  // Pointer type represented as an unsigned integer.
  PtrUInt = NativeUInt;
{$ENDIF}

{$IFNDEF FPC}
  // Pointer to @link(SizeInt).
  PSizeInt = ^SizeInt;

  // Signed integer data type having the same size as pointer on the given platform.
  SizeInt = PtrInt;

  // Pointer to @link(SizeUInt).
  PSizeUInt = ^SizeUInt;

  // Unsigned integer data type having the same size as pointer on the given platform.
  SizeUInt = PtrUInt;
{$ENDIF}

  // Pointer to @link(VectorFloat).
  PVectorFloat = ^VectorFloat;

  // Floating-point data type that is commonly used in the framework.
  VectorFloat = Single;

  // Pointer to @link(VectorInt).
  PVectorInt = ^VectorInt;

  // Signed integer data type that is commonly used in the framework.
  VectorInt = Integer;

  // Pointer to @link(VectorUInt).
  PVectorUInt = ^VectorUInt;

  // Unsigned integer data type that is commonly used in the framework.
  VectorUInt = Cardinal;

{$INCLUDE PXT.Enums.inc}

type
  // Data type meant for storing cross-platform handles. This is a signed integer with the same size as
  // pointer on the given platform.
  TUntypedHandle = NativeInt;

  // Pointer to @link(TLibraryBool).
  PLibraryBool = ^TLibraryBool;

  // Boolean type returned by the library.
  TLibraryBool = ByteBool;

  // Custom object handle.
  TUntypedObjectHandle = Pointer;

  // Custom model object's payload.
  TObjectPayload = Pointer;

const
  // A special value that determines precision limit when comparing vectors and coordinates.
  VectorEpsilon = 1E-6;

{$ENDREGION}
{$REGION 'TIntColor'}

type
  // Pointer to @link(TIntColorValue).
  PIntColorValue = ^TIntColorValue;

  // Raw (untyped) color value is represented as a raw 32-bit unsigned integer, with components allocated
  // according to @italic(TPixelFormat.ARGB8) format.
  TIntColorValue = {$IFDEF NEXTGEN}Cardinal{$ELSE}LongWord{$ENDIF};

  // Pointer to @link(TIntColor).
  PIntColor = ^TIntColor;

  // General-purpose color value that is represented as 32-bit unsigned integer, with components allocated
  // according to @italic(TPixelFormat.ARGB8) format.
  TIntColor = {$IFDEF NEXTGEN}Cardinal{$ELSE}LongWord{$ENDIF};

  // Pointer to @link(TIntColorRec).
  PIntColorRec = ^TIntColorRec;

  // Alternative representation of @link(TIntColor), where each element can be accessed as an individual
  // value. This can be safely typecast to @link(TIntColor) and vice-versa.
  TIntColorRec = record
    case Cardinal of
      0: (// Blue value ranging from 0 (no intensity) to 255 (fully intense).
          Blue: Byte;
          // Green value ranging from 0 (no intensity) to 255 (fully intense).
          Green: Byte;
          // Red value ranging from 0 (no intensity) to 255 (fully intense).
          Red: Byte;
          // Alpha-channel value ranging from 0 (translucent) to 255 (opaque).
          Alpha: Byte;);
      1: // Values represented as an array, with indexes corresponding to blue (0), green (1), red (2) and
         // alpha-channel (3).
         (Values: packed array[0..3] of Byte);
  end;

  // Pointer to @link(TIntColorPalette).
  PIntColorPalette = ^TIntColorPalette;

  // A fixed palette of 256 colors, typically used to emulate legacy 8-bit indexed modes.
  TIntColorPalette = array[0..255] of TIntColor;

const
  // Predefined constant for opaque Black color.
  IntColorBlack = $FF000000;

  // Predefined constant for opaque White color.
  IntColorWhite = $FFFFFFFF;

  // Predefined constant for translucent Black color.
  IntColorTranslucentBlack = $00000000;

  // Predefined constant for translucent White color.
  IntColorTranslucentWhite = $00FFFFFF;

{$ENDREGION}
{$REGION 'TColorPair'}

type
  // Pointer to @link(TColorPair).
  PColorPair = ^TColorPair;

  // A combination of two colors, primarily used for displaying text with the first color being on top and
  // the second being on bottom. The format for specifying colors is defined as @italic(TPixelFormat.ARGB8).
  TColorPair = record
  public
    { @exclude } class operator Implicit(const AColor: TIntColor): TColorPair; inline;
    { @exclude } class operator Equal(const AColor1, AColor2: TColorPair): Boolean;
    { @exclude } class operator NotEqual(const AColor1, AColor2: TColorPair): Boolean; inline;

    // Returns @True if both values equal to zero.
    function Empty: Boolean;

    // Returns @True if two colors are different at least in one of their red, green, blue or alpha
    // components.
    function HasGradient: Boolean;

    // Returns @True if at least one of the colors has non-zero alpha channel.
    function HasAlpha: Boolean;

    // Returns @True if at least one of the colors has alpha channel less than 255.
    function HasTransparency: Boolean;
  public
    case Cardinal of
      0: (// First color entry, which can be reinterpreted as top or left color depending on context.
          First: TIntColor;

          // Second color entry, which can be reinterpreted as bottom or right color depending on context.
          Second: TIntColor;);
      1:// Two color pair represented as an array.
        (Values: array[0..1] of TIntColor;);
  end;

  // Helper module for TColorPair.
  TColorPairHelper = record helper for TColorPair
  public const
    // Predefined constant for a pair of opaque Black colors.
    Black: TColorPair = (First: $FF000000; Second: $FF000000);

    // Predefined constant for a pair of opaque White colors.
    White: TColorPair = (First: $FFFFFFFF; Second: $FFFFFFFF);

    // Predefined constant for a pair of translucent Black colors.
    TranslucentBlack: TColorPair = (First: $00000000; Second: $00000000);

    // Predefined constant for a pair of translucent White colors.
    TranslucentWhite: TColorPair = (First: $00FFFFFF; Second: $00FFFFFF);
  end;

// Creates two 32-bit RGBA color gradient from specified pair of values.
function ColorPair(const AFirst, ASecond: TIntColor): TColorPair; overload; inline;

// Creates two 32-bit RGBA color gradient with both values set to specified color.
function ColorPair(const AColor: TIntColor): TColorPair; overload; inline;

// Modifies alpha-channel of a given color pair multiplying it with the given alpha value.
function ColorPairAlpha(const AColorPair: TColorPair; const AAlpha: Integer): TColorPair;

{$ENDREGION}
{$REGION 'TColorRect'}

type
  // Pointer to @link(TColorRect).
  PColorRect = ^TColorRect;

  // A combination of four colors, primarily used for displaying colored quads, where each color corresponds
  // to top/left,top/right, bottom/right and bottom/left accordingly (clockwise). The format for specifying
  // colors is defined as @italic(TPixelFormat.ARGB8).
  TColorRect = record
  public
    { @exclude } class operator Implicit(const AColor: TIntColor): TColorRect; inline;
    { @exclude } class operator Equal(const AColor1, AColor2: TColorRect): Boolean;
    { @exclude } class operator NotEqual(const AColor1, AColor2: TColorRect): Boolean; inline;

    // Returns @True if each of four values equals to zero.
    function Empty: Boolean;

    // Returns @True if at least one of four colors is different from others in red, green, blue or alpha
    // components.
    function HasGradient: Boolean;

    // Returns @True if at least one of the colors has non-zero alpha channel.
    function HasAlpha: Boolean;

    // Returns @True if at least one of the colors has alpha channel less than 255.
    function HasTransparency: Boolean;
  public
    case Cardinal of
      0: (// Color corresponding to top/left corner.
          TopLeft: TIntColor;
          // Color corresponding to top/right corner.
          TopRight: TIntColor;
          // Color corresponding to bottom/right corner.
          BottomRight: TIntColor;
          // Color corresponding to bottom/left corner.
          BottomLeft: TIntColor;
        );
      1: // Four colors represented as an array.
        (Values: array[0..3] of TIntColor);
  end;

  // Helper module for TColorRect.
  TColorRectHelper = record helper for TColorRect
  public const
    // Predefined constant for four opaque Black colors.
    Black: TColorRect = (TopLeft: $FF000000; TopRight: $FF000000; BottomRight: $FF000000;
      BottomLeft: $FF000000);

    // Predefined constant for four opaque White colors.
    White: TColorRect = (TopLeft: $FFFFFFFF; TopRight: $FFFFFFFF; BottomRight: $FFFFFFFF;
      BottomLeft: $FFFFFFFF);

    // Predefined constant for four translucent Black colors.
    TranslucentBlack: TColorRect = (TopLeft: $00000000; TopRight: $00000000; BottomRight: $00000000;
      BottomLeft: $00000000);

    // Predefined constant for four translucent White colors.
    TranslucentWhite: TColorRect = (TopLeft: $00FFFFFF; TopRight: $00FFFFFF; BottomRight: $00FFFFFF;
      BottomLeft: $00FFFFFF);
  end;

// Creates a construct of four colors using individual components.
function ColorRect(const ATopLeft, ATopRight, ABottomRight,
  ABottomLeft: TIntColor): TColorRect; overload; inline;

// Creates a construct of four colors having the same component in each corner.
function ColorRect(const AColor: TIntColor): TColorRect; overload; inline;

// Creates a construct of four colors from two color pair to create horizontal gradient.
function ColorRectH(const AColor: TColorPair): TColorRect; overload; inline;

// Creates a construct of four colors from two color values to create horizontal gradient.
function ColorRectH(const ALeft, ARight: TIntColor): TColorRect; overload; inline;

// Creates a construct of four colors from two color pair to create vertical gradient.
function ColorRectV(const AColor: TColorPair): TColorRect; overload; inline;

// Creates a construct of four colors from two color values to create vertical gradient.
function ColorRectV(const ATop, ABottom: TIntColor): TColorRect; overload; inline;

// Modifies alpha-channel of a given color rectangle multiplying it with the given alpha value.
function ColorRectAlpha(const AColorRect: TColorRect; const AAlpha: Integer): TColorRect;

{$ENDREGION}
{$REGION 'TFloatColorRGB'}

type
  // Pointer to @link(TFloatColorRGB).
  PFloatColorRGB = ^TFloatColorRGB;

  // A three-component special high-precision color value that has each individual component represented
  // as 32-bit floating-point value in range of [0, 1]. Although components may have values outside of
  // aforementioned range, such colors cannot be reliably displayed on the screen.
  TFloatColorRGB = record
  public
    { @exclude } class operator Add(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
    { @exclude } class operator Subtract(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
    { @exclude } class operator Multiply(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
    { @exclude } class operator Divide(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
    { @exclude } class operator Multiply(const AColor: TFloatColorRGB; const ATheta: VectorFloat): TFloatColorRGB;
    { @exclude } class operator Multiply(const ATheta: VectorFloat; const AColor: TFloatColorRGB): TFloatColorRGB;
    { @exclude } class operator Divide(const AColor: TFloatColorRGB; const ATheta: VectorFloat): TFloatColorRGB;
    { @exclude } class operator Equal(const AColor1, AColor2: TFloatColorRGB): Boolean;
    { @exclude } class operator NotEqual(const AColor1, AColor2: TFloatColorRGB): Boolean; inline;

    // Tests whether all values of the color are not NaNs or infinities.
    function Defined: Boolean;

    // Inverts current Acolor by applying formula "Xn = 1.0 - X" for each component.
    function Invert: TFloatColorRGB;

    // Computes average between current and destination floating-point Acolor values.
    function Average(const AColor: TFloatColorRGB): TFloatColorRGB;

    // Computes Alpha-blending between current and destination floating-point colors values.
    //   @italic(AAlpha) can be in [0..1] range.
    function Lerp(const AColor: TFloatColorRGB; const AAlpha: VectorFloat): TFloatColorRGB;

    // Returns grayscale value in range of [0..1] from the current color value. The resulting value can be
    // considered the color's @italic(luma). The alpha-channel is ignored.
    function Gray: VectorFloat;

    // Clamps each component of current color to [0, 1] range.
    function Saturate: TFloatColorRGB;

    // Converts current floating-point color to 32-bit integer representation.
    function ToInt: TIntColor;
  public
    case Cardinal of
      0: (// Red value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Red: VectorFloat;
          // Green value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Green: VectorFloat;
          // Blue value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Blue: VectorFloat;);
      1: // Values represented as an array, with indexes corresponding to red (0), green (1) and blue (2).
         (Values: array[0..2] of VectorFloat);
  end;

  // Helper module for TFloatColorRGB.
  TFloatColorRGBHelper = record helper for TFloatColorRGB
  public const
    // Predefined constant for opaque Black color.
    Black: TFloatColorRGB = (Red: 0.0; Green: 0.0; Blue: 0.0);

    // Predefined constant for opaque White color.
    White: TFloatColorRGB = (Red: 1.0; Green: 1.0; Blue: 1.0);
  end;

// Creates floating-point color from its 32-bit integer representation.
function FloatColorRGB(const AColor: TIntColor): TFloatColorRGB; overload;

// Creates floating-point color using specified individual components for red, green and blue.
function FloatColorRGB(const ARed, AGreen, ABlue: VectorFloat): TFloatColorRGB; overload;

{$ENDREGION}
{$REGION 'TFloatColor'}

type
  // Pointer to @link(TFloatColor).
  PFloatColor = ^TFloatColor;

  // A special high-precision color value that has each individual component represented as 32-bit
  // floating-point value in range of [0, 1]. Although components may have values outside of aforementioned
  // range, such colors cannot be reliably displayed on the screen.
  TFloatColor = record
  public
    { @exclude } class operator Add(const AColor1, AColor2: TFloatColor): TFloatColor;
    { @exclude } class operator Subtract(const AColor1, AColor2: TFloatColor): TFloatColor;
    { @exclude } class operator Multiply(const AColor1, AColor2: TFloatColor): TFloatColor;
    { @exclude } class operator Divide(const AColor1, AColor2: TFloatColor): TFloatColor;
    { @exclude } class operator Multiply(const AColor: TFloatColor; const ATheta: VectorFloat): TFloatColor;
    { @exclude } class operator Multiply(const ATheta: VectorFloat; const AColor: TFloatColor): TFloatColor;
    { @exclude } class operator Divide(const AColor: TFloatColor; const ATheta: VectorFloat): TFloatColor;
    { @exclude } class operator Equal(const AColor1, AColor2: TFloatColor): Boolean;
    { @exclude } class operator NotEqual(const AColor1, AColor2: TFloatColor): Boolean; inline;

    // Inverts current color by applying formula "Xn = 1.0 - X" for each component.
    function Invert: TFloatColor;

    // Takes current color with unpremultiplied alpha and multiplies each of red, green, and blue components
    // by its alpha channel, resulting in premultiplied alpha color.
    function PremultiplyAlpha: TFloatColor;

    // Takes current color with premultiplied alpha channel and divides each of its red, green, and blue
    // components by alpha, resulting in unpremultiplied alpha color.
    function UnpremultiplyAlpha: TFloatColor;

    // Computes average between current and destination floating-point color values.
    function Average(const AColor: TFloatColor): TFloatColor;

    // Computes alpha-blending between current and destination floating-point colors values.
    //   @italic(Alpha) can be in [0..1] range.
    function Lerp(const AColor: TFloatColor; const AAlpha: VectorFloat): TFloatColor;

    // Returns grayscale value in range of [0..1] from the current color value. The resulting value can be
    // considered the color's @italic(luma). The alpha-channel is ignored.
    function Gray: VectorFloat;

    // Clamps each component of current color to [0, 1] range.
    function Saturate: TFloatColor;

    // Converts current floating-point color to 32-bit integer representation.
    function ToInt: TIntColor;

    // Returns color's red, green and blue components as a three-way floating-point color.
    function RGB: TFloatColorRGB; inline;
  public
    case Cardinal of
      0: (// Red value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Red: VectorFloat;
          // Green value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Green: VectorFloat;
          // Blue value ranging from 0.0 (no intensity) to 1.0 (fully intense).
          Blue: VectorFloat;
          // Alpha-channel value ranging from 0.0 (translucent) to 1.0 (opaque).
          Alpha: VectorFloat;);
      1: // Values represented as an array, with indexes corresponding to red (0), green (1), blue (2) and
         // alpha-channel (3).
         (Values: array[0..3] of VectorFloat);
  end;

  // Helper module for TFloatColor.
  TFloatColorHelper = record helper for TFloatColor
  public const
    // Predefined constant for opaque Black color.
    Black: TFloatColor = (Red: 0.0; Green: 0.0; Blue: 0.0; Alpha: 1.0);

    // Predefined constant for opaque White color.
    White: TFloatColor = (Red: 1.0; Green: 1.0; Blue: 1.0; Alpha: 1.0);

    // Predefined constant for translucent Black color.
    TranslucentBlack: TFloatColor = (Red: 0.0; Green: 0.0; Blue: 0.0; Alpha: 0.0);

    // Predefined constant for translucent White color.
    TranslucentWhite: TFloatColor = (Red: 1.0; Green: 1.0; Blue: 1.0; Alpha: 0.0);
  end;

  // Pointer to @link(TFloatColorRect).
  PFloatColorRect = ^TFloatColorRect;

  // A combination of four high-precision floating-point colors, primarily used for displaying colored quads,
  // where each color corresponds to top/left,top/right, bottom/right and bottom/left accordingly (clockwise).
  // The format for specifying colors is defined as @italic(TPixelFormat.ARGB8).
  TFloatColorRect = record
  public
    { @exclude } class operator Implicit(const AColorRect: TColorRect): TFloatColorRect; inline;

    // Creates color rectangle gradient with the given values.
    class function Create(const ATopLeft, ATopRight, ABottomRight,
      ABottomLeft: TFloatColor): TFloatColorRect; static; inline;

    case Cardinal of
      0: (// Color corresponding to top/left corner.
          TopLeft: TFloatColor;
          // Color corresponding to top/right corner.
          TopRight: TFloatColor;
          // Color corresponding to bottom/right corner.
          BottomRight: TFloatColor;
          // Color corresponding to bottom/left corner.
          BottomLeft: TFloatColor;
        );
      1: // Four colors represented as an array.
        (Values: array[0..3] of TFloatColor);
  end;

// Creates floating-point color from its 32-bit integer representation.
function FloatColor(const AColor: TIntColor): TFloatColor; overload;

// Creates floating-point color using specified individual components for red, green, blue and alpha channel.
function FloatColor(const ARed, AGreen, ABlue: VectorFloat;
  const AAlpha: VectorFloat = 1.0): TFloatColor; overload;

// Creates floating-point color from an existing RGB color and separate alpha component.
function FloatColor(const AColor: TFloatColorRGB; const AAlpha: VectorFloat = 1.0): TFloatColor; overload;

{$ENDREGION}
{$REGION 'TPoint2i declarations'}

type
  // Pointer to @link(TPoint2i).
  PPoint2i = ^TPoint2i;

  // 2D integer vector.
  TPoint2i = record
    // The coordinate in 2D space.
    X, Y: VectorInt;

    { @exclude } class operator Add(const APoint1, APoint2: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Subtract(const APoint1, APoint2: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Multiply(const APoint1, APoint2: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Divide(const APoint1, APoint2: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Negative(const APoint: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Multiply(const APoint: TPoint2i; const ATheta: VectorInt): TPoint2i; inline;
    { @exclude } class operator Multiply(const ATheta: VectorInt; const APoint: TPoint2i): TPoint2i; inline;
    { @exclude } class operator Divide(const APoint: TPoint2i; const ATheta: VectorInt): TPoint2i; inline;
    { @exclude } class operator Divide(const APoint: TPoint2i; const ATheta: VectorFloat): TPoint2i; inline;
    { @exclude } class operator Equal(const APoint1, APoint2: TPoint2i): Boolean; inline;
    { @exclude } class operator NotEqual(const APoint1, APoint2: TPoint2i): Boolean; inline;

    // Returns vector with X and Y swapped.
    function Swap: TPoint2i; inline;

    // Tests whether both X and Y are zero.
    function Empty: Boolean; inline;

    // Returns length of current vector.
    function Length: VectorFloat; inline;

    // Calculates distance between current and given points.
    function Distance(const APoint: TPoint2i): VectorFloat; inline;

    // Calculates square distance between current and given points.
    function DistanceSquare(const APoint: TPoint2i): VectorInt; inline;

    // Returns an angle (in radians) at which the current vector is pointing at.
    function Angle: VectorFloat; inline;

    // Calculates a dot product between current and the specified 2D vector. The dot product is an indirect
    // measure of the angle between two vectors.
    function Dot(const APoint: TPoint2i): VectorInt; inline;

    // Calculates a cross product between current and the specified 2D vector, or analog of thereof.
    function Cross(const APoint: TPoint2i): VectorInt; inline;

    // Interpolates between current and destination 2D integer vectors.
    //   @param(Point The destination vector to be used in the interpolation)
    //   @param(Theta The mixture of the two vectors with the a range of [0..1].)
    function Lerp(const APoint: TPoint2i; const ATheta: VectorFloat): TPoint2i;

    // Tests whether current point is inside the triangle specified by given three vertices.
    function InsideTriangle(const AVertex1, AVertex2, AVertex3: TPoint2i): Boolean;
  end;

  // Helper module for TPoint2i.
  TPoint2iHelper = record helper for TPoint2i
  public const
    // Predefined constant, where X and Y are zero.
    Zero: TPoint2i = (X: 0; Y: 0);

    // Predefined constant, where X and Y are one.
    Unity: TPoint2i = (X: 1; Y: 1);

    // Predefined constant, where X = 1 and Y = 0.
    AxisX: TPoint2i = (X: 1; Y: 0);

    // Predefined constant, where X = 0 and Y = 1.
    AxisY: TPoint2i = (X: 0; Y: 1);

    // Predefined constant for "negative infinity".
    MinusInfinity: TPoint2i = (X: Low(VectorInt) + 1; Y: Low(VectorInt) + 1);

    // Predefined constant for "positive infinity".
    PlusInfinity: TPoint2i = (X: High(VectorInt); Y: High(VectorInt));

    // Predefined constant that can be interpreted as "undefined value".
    Undefined: TPoint2i = (X: Low(VectorInt); Y: Low(VectorInt));
  end;

// Creates a @link(TPoint2i) record using the specified coordinates.
function Point2i(const AX, AY: VectorInt): TPoint2i;

{$ENDREGION}
{$REGION 'TPoint2f declarations'}

type
  // Pointer to @link(TPoint2f).
  PPoint2f = ^TPoint2f;

  // 2D floating-point vector.
  TPoint2f = record
    // The coordinate in 2D space.
    X, Y: VectorFloat;

    { @exclude } class operator Add(const APoint1, APoint2: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Subtract(const APoint1, APoint2: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Multiply(const APoint1, APoint2: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Divide(const APoint1, APoint2: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Negative(const APoint: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Multiply(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f; inline;
    { @exclude } class operator Multiply(const ATheta: VectorFloat; const APoint: TPoint2f): TPoint2f; inline;
    { @exclude } class operator Divide(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f; inline;
    { @exclude } class operator Divide(const APoint: TPoint2f; const ATheta: Integer): TPoint2f; inline;
    { @exclude } class operator Implicit(const APoint: TPoint2i): TPoint2f; inline;
    { @exclude } class operator Equal(const APoint1, APoint2: TPoint2f): Boolean; inline;
    { @exclude } class operator NotEqual(const APoint1, APoint2: TPoint2f): Boolean; inline;

  {$IFDEF POINT_FLOAT_TO_INT_IMPLICIT}
    // This implicit conversion is only allowed as a compiler directive because it causes ambiguity and
    // precision problems when excessively used. In addition, it can make code more confusing.
    { @exclude } class operator Implicit(const APoint: TPoint2f): TPoint2i;
  {$ENDIF}

    // Returns vector with X and Y swapped.
    function Swap: TPoint2f; inline;

    // Tests whether both X and Y are nearly zero.
    function Empty: Boolean; inline;

    // Returns the length of current 2D vector.
    function Length: VectorFloat; inline;

    // Calculates distance between current and given points.
    function Distance(const APoint: TPoint2f): VectorFloat; inline;

    // Calculates square distance between current and given points.
    function DistanceSquare(const APoint: TPoint2f): VectorFloat; inline;

    // Returns an angle (in radians) at which current is pointing at.
    function Angle: VectorFloat; inline;

    // Calculates a dot product between current and the specified 2D vectors. The dot product is an indirect
    // measure of the angle between two vectors.
    function Dot(const APoint: TPoint2f): VectorFloat; inline;

    // Calculates a cross product between current and the specified 2D vectors, or analog of thereof.
    function Cross(const APoint: TPoint2f): VectorFloat; inline;

    // Normalizes current vector to unity length. If the vector is of zero length, it will remain unchanged.
    function Normalize: TPoint2f;

    // Interpolates between current and destination 2D vectors.
    //   @param(Point The destination vector to be used in the interpolation)
    //   @param(Theta The mixture of the two vectors with the a range of [0..1].) }
    function Lerp(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f;

    // Tests whether current point is inside the triangle specified by given three vertices.
    function InsideTriangle(const AVertex1, AVertex2, AVertex3: TPoint2f): Boolean;

    // Converts @link(TPoint2f) to @link(TPoint2i) by using floating-point rounding.
    function ToInt: TPoint2i;
  end;

  // Helper module for TPoint2f.
  TPoint2fHelper = record helper for TPoint2f
  public const
    // Predefined constant, where X and Y are zero.
    Zero: TPoint2f = (X: 0.0; Y: 0.0);

    // Predefined constant, where X and Y are one.
    Unity: TPoint2f = (X: 1.0; Y: 1.0);

    // Predefined constant, where X = 1 and Y = 0.
    AxisX: TPoint2f = (X: 1.0; Y: 0.0);

    // Predefined constant, where X = 0 and Y = 1.
    AxisY: TPoint2f = (X: 0.0; Y: 1.0);
  end;

// Creates a @link(TPoint2f) record using the specified coordinates.
function Point2f(const AX, AY: VectorFloat): TPoint2f; inline;

{$ENDREGION}
{$REGION 'TVector3i declarations'}

type
  // Pointer to @link(TVector3iArray).
  PVector3iArray = ^TVector3iArray;

  // Representation of @link(TVector3i) as an array.
  TVector3iArray = array[0..2] of VectorInt;

{$ENDREGION}
{$REGION 'TVector3f declarations'}

type
  // Pointer to @link(TVector3fArray).
  PVector3fArray = ^TVector3fArray;

  // Representation of @link(TVector3f) as an array.
  TVector3fArray = array[0..2] of VectorFloat;

  // Pointer to @link(TVector3f).
  PVector3f = ^TVector3f;

  // 3D floating-point vector.
  TVector3f = record
    // The coordinate in 3D space.
    X, Y, Z: VectorFloat;

    { @exclude } class operator Add(const AVector1, AVector2: TVector3f): TVector3f; inline;
    { @exclude } class operator Subtract(const AVector1, AVector2: TVector3f): TVector3f; inline;
    { @exclude } class operator Multiply(const AVector1, AVector2: TVector3f): TVector3f; inline;
    { @exclude } class operator Divide(const AVector1, AVector2: TVector3f): TVector3f; inline;
    { @exclude } class operator Negative(const AVector: TVector3f): TVector3f; inline;
    { @exclude } class operator Multiply(const AVector: TVector3f;
      const ATheta: VectorFloat): TVector3f; inline;
    { @exclude } class operator Multiply(const ATheta: VectorFloat;
      const AVector: TVector3f): TVector3f; inline;
    { @exclude } class operator Divide(const AVector: TVector3f; const ATheta: VectorFloat): TVector3f; inline;
    { @exclude } class operator Equal(const AVector1, AVector2: TVector3f): Boolean;
    { @exclude } class operator NotEqual(const AVector1, AVector2: TVector3f): Boolean; inline;

    // Tests whether X, Y and Z are nearly zero.
    function Empty: Boolean;

    // Returns length of current vector.
    function Length: VectorFloat;

    // Calculates distance between current and given vectors.
    function Distance(const AVector: TVector3f): VectorFloat;

    // Calculates square distance between current and given vectors.
    function DistanceSquare(const AVector: TVector3f): VectorFloat; inline;

    // Calculates a dot product between current and the specified 3D vector. The dot product is an indirect
    // measure of angle between two vectors.
    function Dot(const AVector: TVector3f): VectorFloat; inline;

    // Calculates a cross product between current and the specified 3D vector. The resulting vector is
    // perpendicular to both vectors and normal to the plane containing them.
    function Cross(const AVector: TVector3f): TVector3f;

    // Calculates angle between current and the specified 3D vector. The returned value has range of [0..Pi].
    function Angle(const AVector: TVector3f): VectorFloat;

    // Normalizes current vector to unity length. If current length is zero, the same vector is returned.
    function Normalize: TVector3f;

    // Calculates a portion of current vector that is parallel to the direction vector.
    function Parallel(const ADirection: TVector3f): TVector3f;

    // Calculates a portion of current vector that is perpendicular to the direction vector.
    function Perpendicular(const ADirection: TVector3f): TVector3f;

    // Calculates 3D vector that is a reflection of current vector from surface given by specified normal.
    function Reflect(const ANormal: TVector3f): TVector3f;

    // Interpolates between current and destination 3D vectors.
    //   @param(Vector The destination vector to be used in the interpolation)
    //   @param(Theta The mixture of the two vectors with the a range of [0..1].)
    function Lerp(const AVector: TVector3f; const ATheta: VectorFloat): TVector3f;

    // Returns (x, y) portion of 3D vector as @link(TPoint2f).
    function GetXY: TPoint2f; inline;
  end;

  // Helper module for TVector3f.
  TVector3fHelper = record helper for TVector3f
  public const
    // Predefined constant, where X, Y and Z are zero.
    Zero: TVector3f = (X: 0.0; Y: 0.0; Z: 0.0);

    // Predefined constant, where X, Y and Z are one.
    Unity: TVector3f = (X: 1.0; Y: 1.0; Z: 1.0);

    // Predefined constant, where X = 1, Y = 0 and Z = 0.
    AxisX: TVector3f = (X: 1.0; Y: 0.0; Z: 0.0);

    // Predefined constant, where X = 0, Y = 1 and Z = 0.
    AxisY: TVector3f = (X: 0.0; Y: 1.0; Z: 0.0);

    // Predefined constant, where X = 0, Y = 0 and Z = 1.
    AxisZ: TVector3f = (X: 0.0; Y: 0.0; Z: 1.0);
  end;

// Creates @link(TVector3f) record using the specified coordinates.
function Vector3f(const AX, AY, AZ: VectorFloat): TVector3f; overload; inline;

// Creates @link(TVector3f) record using 2D vector and specified Z coordinate.
function Vector3f(const APoint: TPoint2f; const AZ: VectorFloat = 0.0): TVector3f; overload; inline;

{$ENDREGION}
{$REGION 'TVector4f declarations'}

type
  // Pointer to @link(TVector4f).
  PVector4f = ^TVector4f;

  // 4D (3D + w) floating-point vector.
  TVector4f = record
    // The coordinate in 3D space.
    X, Y, Z: VectorFloat;

    // Homogeneous transform coordinate, mostly used for perspective projection.
    // Typically, this component is set to 1.0.
    W: VectorFloat;

    { @exclude } class operator Add(const AVector1, AVector2: TVector4f): TVector4f;
    { @exclude } class operator Subtract(const AVector1, AVector2: TVector4f): TVector4f;
    { @exclude } class operator Multiply(const AVector1, AVector2: TVector4f): TVector4f;
    { @exclude } class operator Divide(const AVector1, AVector2: TVector4f): TVector4f;
    { @exclude } class operator Negative(const AVector: TVector4f): TVector4f; inline;
    { @exclude } class operator Multiply(const AVector: TVector4f;
      const ATheta: VectorFloat): TVector4f; inline;
    { @exclude } class operator Multiply(const ATheta: VectorFloat;
      const AVector: TVector4f): TVector4f; inline;
    { @exclude } class operator Divide(const AVector: TVector4f; const ATheta: VectorFloat): TVector4f; inline;
    { @exclude } class operator Equal(const AVector1, AVector2: TVector4f): Boolean;
    { @exclude } class operator NotEqual(const AVector1, AVector2: TVector4f): Boolean; inline;

    // Interpolates between current and the specified 4D vector.
    //   @param(Vector The destination vector to be used in the interpolation)
    //   @param(Theta The mixture of the two vectors with the a range of [0..1].) }
    function Lerp(const AVector: TVector4f; const ATheta: VectorFloat): TVector4f;

    // Returns (X, Y, Z) portion of 4D vector.
    function GetXYZ: TVector3f; inline;

    // Returns (X, Y, Z) portion of current 4D (3D + W) vector, projecting to W = 1 whenever necessary.
    function ProjectToXYZ: TVector3f;
  end;

  // Helper module for TVector4f.
  TVector4fHelper = record helper for TVector4f
  public const
    // Predefined constant, where all components are zero.
    Zero: TVector4f = (X: 0.0; Y: 0.0; Z: 0.0; W: 0.0);

    // Predefined constant, where X, Y and Z are zero, while W = 1.
    ZeroH: TVector4f = (X: 0.0; Y: 0.0; Z: 0.0; W: 1.0);

    // Predefined constant, where all components are one.
    Unity: TVector4f = (X: 1.0; Y: 1.0; Z: 1.0; W: 1.0);

    // Predefined constant, where X = 1, Y = 0, Z = 0 and W = 1.
    AxisXH: TVector4f = (X: 1.0; Y: 0.0; Z: 0.0; W: 1.0);

    // Predefined constant, where X = 0, Y = 1, Z = 0 and W = 1.
    AxisYH: TVector4f = (X: 0.0; Y: 1.0; Z: 0.0; W: 1.0);

    // Predefined constant, where X = 0, Y = 0, Z = 1 and W = 1.
    AxisZH: TVector4f = (X: 0.0; Y: 0.0; Z: 1.0; W: 1.0);

    // Predefined constant, where X = 1, Y = 0, Z = 0 and W = 0.
    AxisX: TVector4f = (X: 1.0; Y: 0.0; Z: 0.0; W: 0.0);

    // Predefined constant, where X = 0, Y = 1, Z = 0 and W = 0.
    AxisY: TVector4f = (X: 0.0; Y: 1.0; Z: 0.0; W: 0.0);

    // Predefined constant, where X = 0, Y = 0, Z = 1 and W = 0.
    AxisZ: TVector4f = (X: 0.0; Y: 0.0; Z: 1.0; W: 0.0);

    // Predefined constant, where X = 0, Y = 0, Z = 0 and W = 1.
    AxisW: TVector4f = (X: 0.0; Y: 0.0; Z: 0.0; W: 1.0);
  end;

// Creates a @link(TVector4f) record using the specified X, Y, Z and W coordinates.
function Vector4f(const AX, AY, AZ: VectorFloat; const AW: VectorFloat = 1.0): TVector4f; overload; inline;

// Creates a @link(TVector4f) record using the specified 3D vector and W coordinate.
function Vector4f(const AVector: TVector3f; const AW: VectorFloat = 1.0): TVector4f; overload; inline;

// Creates a @link(TVector4f) record using the specified 2D point, Z and W coordinates.
function Vector4f(const APoint: TPoint2f; const AZ: VectorFloat = 0.0;
  const AW: VectorFloat = 1.0): TVector4f; overload; inline;

{$ENDREGION}
{$REGION 'TMatrix3x2f declarations'}

type
  // Pointer to @link(TMatrix3x2f).
  PMatrix3x2f = ^TMatrix3x2f;

  // 3x2 transformation matrix.
  TMatrix3x2f = record
    // Individual matrix values.
    Data: array[0..2, 0..1] of VectorFloat;

    { @exclude } class operator Add(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
    { @exclude } class operator Subtract(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
    { @exclude } class operator Multiply(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
    { @exclude } class operator Multiply(const AMatrix: TMatrix3x2f; const ATheta: VectorFloat): TMatrix3x2f;
    { @exclude } class operator Multiply(const ATheta: VectorFloat;
      const AMatrix: TMatrix3x2f): TMatrix3x2f; inline;
    { @exclude } class operator Divide(const AMatrix: TMatrix3x2f; const ATheta: VectorFloat): TMatrix3x2f;
    { @exclude } class operator Multiply(const APoint: TPoint2f; const AMatrix: TMatrix3x2f): TPoint2f;

    // Tests whether current matrix has all components nearly zero.
    function Empty: Boolean;

    // Calculates determinant of current matrix (as if it was 3x3 with right column being identity).
    function Determinant: VectorFloat;

    // Calculates inverse matrix of the current matrix (as if it was 3x3 with right column being identity).
    function Inverse: TMatrix3x2f;

    // Calculates inverse and transpose matrix of the current matrix as if it was 2x2.
    function InverseTranspose: TMatrix3x2f;

    // Creates a 2D scaling matrix with specified coefficients.
    class function Scale(const AScale: TPoint2f): TMatrix3x2f; overload; static;

    // Creates a 2D scaling matrix with specified individual coefficients.
    class function Scale(const AX, AY: VectorFloat): TMatrix3x2f; overload; static; inline;

    // Creates a 2D scaling matrix with with X and Y equal to the specified coefficient.
    class function Scale(const AScale: VectorFloat): TMatrix3x2f; overload; static; inline;

    // Creates a 2D scaling matrix around a specific center point.
    class function Scale(const ACenter: TPoint2f;
      const AScale: TPoint2f): TMatrix3x2f; overload; static; inline;

    // Creates a 2D translation matrix with specified offset.
    class function Translate(const AOffset: TPoint2f): TMatrix3x2f; overload; static;

    // Creates a 2D translation matrix with specified coordinates.
    class function Translate(const AX, AY: VectorFloat): TMatrix3x2f; overload; static; inline;

    // Creates a 2D rotation matrix with specified angle (in radiants).
    class function Rotate(const AAngle: VectorFloat): TMatrix3x2f; overload; static;

    // Creates a 2D rotation matrix with specified angle (in radiants) around a particular center point.
    class function Rotate(const ACenter: TPoint2f; const AAngle: VectorFloat): TMatrix3x2f; overload; static;

    // Creates a 2D skew matrix around a specific center point.
    class function Skew(const ACenter: TPoint2f; const AAngles: TPoint2f): TMatrix3x2f; static;
  end;

  // Helper module for TMatrix3x2f.
  TMatrix3x2fHelper = record helper for TMatrix3x2f
  public const
    // Predefined constant with values corresponding to @italic(Identity) matrix.
    Identity: TMatrix3x2f = (Data: ((1.0, 0.0), (0.0, 1.0), (0.0, 0.0)));

    // Predefined constant, where all matrix values are zero.
    Zero: TMatrix3x2f = (Data: ((0.0, 0.0), (0.0, 0.0), (0.0, 0.0)));
  end;

{$ENDREGION}
{$REGION 'TMatrix4f declarations'}

type
  // Pointer to @link(TMatrix4f).
  PMatrix4f = ^TMatrix4f;

  // 4x4 transformation matrix.
  TMatrix4f = record
  private
    class function DetSub3(const A1, A2, A3, B1, B2, B3, C1, C2, C3: VectorFloat): VectorFloat; static;
  public
    // Individual matrix values.
    Data: array[0..3, 0..3] of VectorFloat;

    { @exclude } class operator Add(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
    { @exclude } class operator Subtract(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
    { @exclude } class operator Multiply(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
    { @exclude } class operator Multiply(const AMatrix: TMatrix4f; const ATheta: VectorFloat): TMatrix4f;
    { @exclude } class operator Multiply(const ATheta: VectorFloat;
      const AMatrix: TMatrix4f): TMatrix4f; inline;
    { @exclude } class operator Divide(const AMatrix: TMatrix4f; const ATheta: VectorFloat): TMatrix4f;
    { @exclude } class operator Multiply(const AVector: TVector3f; const AMatrix: TMatrix4f): TVector3f;
    { @exclude } class operator Multiply(const AVector: TVector4f; const AMatrix: TMatrix4f): TVector4f;

    // Calculates determinant of current matrix.
    function Determinant: VectorFloat;

    // Assuming that the current matrix is a view matrix, this method calculates the 3D position where
    // the camera (or "eye") is supposedly located.
    function EyePos: TVector3f;

    // Assuming that the specified matrix is a world matrix, this method calculates the 3D position where
    // the object (or "world") is supposedly located.
    function WorldPos: TVector3f;

    // Transposes current matrix. That is, rows become columns and vice-versa.
    function Transpose: TMatrix4f;

    // Calculates adjoint values for the current matrix.
    function Adjoint: TMatrix4f;

    // Calculates inverse of current matrix. In other words, the new matrix will "undo" any transformations
    // that the given matrix would have made.
    function Inverse: TMatrix4f;

    // Multiplies the given 3D vector as if it was 4D (3D + 1) vector by current matrix, then divides each of
    // the resulting components by the resulting value of W (which is then discarded), and converts absolute
    // coordinates to screen pixels by using the given target size.
    function Project(const AVector: TVector3f; const ATargetSize: TPoint2f): TPoint2f;

    // Creates 3D translation matrix with specified offset.
    class function Translate(const AOffset: TVector3f): TMatrix4f; overload; static;

    // Creates 3D translation matrix with specified X, Y and (optional) Z coordinates.
    class function Translate(const AX, AY: VectorFloat;
      const AZ: VectorFloat = 0.0): TMatrix4f; overload; static; inline;

    // Creates 3D rotation matrix around X axis with the specified angle.
    class function RotateX(const AAngle: VectorFloat): TMatrix4f; static;

    // Creates 3D rotation matrix around Y axis with the specified angle.
    class function RotateY(const AAngle: VectorFloat): TMatrix4f; static;

    // Creates 3D rotation matrix around Z axis with the specified angle.
    class function RotateZ(const AAngle: VectorFloat): TMatrix4f; static;

    // Creates 3D rotation matrix around specified axis and angle (in radiants).
    class function Rotate(const AAxis: TVector3f; const AAngle: VectorFloat): TMatrix4f; static;

    // Creates 3D translation matrix with specified coefficients.
    class function Scale(const AScale: TVector3f): TMatrix4f; overload; static;

    // Creates 3D translation matrix with specified X, Y and (optional) Z coefficients.
    class function Scale(const AX, AY: VectorFloat;
      const AZ: VectorFloat = 0.0): TMatrix4f; overload; static; inline;

    // Creates 3D translation matrix with X, Y and Z equal to the specified coefficient.
    class function Scale(const AScale: VectorFloat): TMatrix4f; overload; static; inline;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically heading, pitch
    // and Bank. Each of the components is specified individually.
    class function HeadingPitchBank(const AHeading, APitch, ABank: VectorFloat): TMatrix4f; overload; static;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically heading, pitch
    // and Bank. The components are taken from the specified vector with Y corresponding to heading, X to
    // pitch and Z to bank.
    class function HeadingPitchBank(const AVector: TVector3f): TMatrix4f; overload; static;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically yaw, pitch and
    // roll. Each of the components is specified individually.
    class function YawPitchRoll(const AYaw, APitch, ARoll: VectorFloat): TMatrix4f; overload; static;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically yaw, pitch and
    // roll. The components are taken from the specified vector with Y corresponding to yaw, X to pitch and Z
    // to roll.
    class function YawPitchRoll(const AVector: TVector3f): TMatrix4f; overload; static;

    // Creates a reflection matrix specified by the given vector defining the orientation of the reflection.
    class function Reflect(const AAxis: TVector3f): TMatrix4f; static;

    // Creates a view matrix that is defined by the camera's position, its target and vertical
    // axis or "ceiling".
    class function LookAt(const AOrigin, ATarget, ACeiling: TVector3f): TMatrix4f; static;

    // Creates perspective projection matrix defined by a field of view on Y axis. This is a common way for
    // typical 3D applications. In 3D shooters special care is to be taken because on wide-screen monitors
    // the visible area will be bigger when using this constructor. The parameters that define the viewed
    // range are important for defining the precision of the depth transformation or a depth-buffer.
    //   @param(FieldOfView The camera's field of view in radians. For example Pi/4.)
    //   @param(AspectRatio The screen's aspect ratio. Can be calculated as y/x.)
    //   @param(MinRange The closest range at which the scene will be viewed.)
    //   @param(MaxRange The farthest range at which the scene will be viewed.)
    //   @param(NegativeDepthRange Whether to use OpenGL [-1, 1] depth range instead of [0, 1].)
    class function PerspectiveFOVY(const AFieldOfView, AAspectRatio, AMinRange,
      AMaxRange: VectorFloat; const ADepthClipNegative: Boolean = False): TMatrix4f; static;

    // Creates perspective projection matrix defined by a field of view on X axis. In 3D shooters the field
    // of view needs to be adjusted to allow more visible area on wide-screen monitors. The parameters that
    // define the viewed range are important for defining the precision of the depth transformation or a
    // depth-buffer.
    //   @param(FieldOfView The camera's field of view in radians. For example Pi/4.)
    //   @param(AspectRatio The screen's aspect ratio. Can be calculated as x/y.)
    //   @param(MinRange The closest range at which the scene will be viewed.)
    //   @param(MaxRange The farthest range at which the scene will be viewed.)
    //   @param(NegativeDepthRange Whether to use OpenGL [-1, 1] depth range instead of [0, 1].) }
    class function PerspectiveFOVX(const AFieldOfView, AAspectRatio, AMinRange,
      AMaxRange: VectorFloat; const ADepthClipNegative: Boolean = False): TMatrix4f; static;

    // Creates perspective projection matrix defined by the viewing volume in 3D space.
    class function PerspectiveVOL(const AWidth, AHeight, AMinRange,
      AMaxRange: VectorFloat): TMatrix4f; static;

    // Creates perspective projection matrix defined by the individual axis's boundaries.
    class function PerspectiveBDS(const ALeft, ARight, ATop, ABottom, AMinRange,
      AMaxRange: VectorFloat): TMatrix4f; static;

    // Creates orthogonal projection matrix defined by the viewing volume in 3D space.
    class function OrthogonalVOL(const AWidth, AHeight, AMinRange,
      AMaxRange: VectorFloat): TMatrix4f; static;

    // Creates orthogonal projection matrix defined by the individual axis's boundaries.
    class function OrthogonalBDS(const ALeft, ARight, ATop, ABottom, AMinRange,
      AMaxRange: VectorFloat): TMatrix4f; static;
  end;

  // Helper module for TMatrix4f.
  TMatrix4fHelper = record helper for TMatrix4f
  public const
    // Predefined constant with values corresponding to @italic(Identity) matrix.
    Identity: TMatrix4f = (Data: ((1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, 1.0, 0.0),
      (0.0, 0.0, 0.0, 1.0)));

    // Predefined constant, where all matrix values are zero.
    Zero: TMatrix4f = (Data: ((0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0),
      (0.0, 0.0, 0.0, 0.0)));

    // Predefined constant for an inversion matrix (to convert handeness).
    Inversion: TMatrix4f = (Data: ((1.0, 0.0, 0.0, 0.0), (0.0, 1.0, 0.0, 0.0), (0.0, 0.0, -1.0, 0.0),
      (0.0, 0.0, 0.0, 1.0)));
  end;

{$ENDREGION}
{$REGION 'TQuaternion declarations'}

type
  // Pointer to @link(TQuaternion).
  PQuaternion = ^TQuaternion;

  // 3D quaternion.
  TQuaternion = record
    // Individual quaternion values.
    X, Y, Z, W: VectorFloat;

    { @exclude } class operator Multiply(const AQuaternion1, AQuaternion2: TQuaternion): TQuaternion;
    { @exclude } class operator Implicit(const AQuaternion: TQuaternion): TMatrix4f;
    { @exclude } class operator Explicit(const AMatrix: TMatrix4f): TQuaternion;
    { @exclude } class operator Equal(const AQuaternion1, AQuaternion2: TQuaternion): Boolean;
    { @exclude } class operator NotEqual(const AQuaternion1, AQuaternion2: TQuaternion): Boolean; inline;

    // Returns the magnitude of current quaternion.
    function Length: VectorFloat;

    // Normalizes the current quaternion. Note that normally quaternions are always normalized (of course,
    // within limits of numerical precision). This function is provided mainly to combat floating point
    // "error creep", which occurs after many successive quaternion operations.
    function Normalize: TQuaternion;

    // Returns rotational angle "theta" that is present in current quaternion.
    function Angle: VectorFloat;

    // Returns rotational axis that is present in current quaternion.
    function Axis: TVector3f;

    // Computes quaternion's conjugate. The resulting quaternion has opposite rotation to the original
    // quaternion.
    function Conjugate: TQuaternion;

    // Computes exponentiation of the given quaternion.
    function Exponentiate(const AExponent: VectorFloat): TQuaternion;

    // Computes the dot product of two given quaternions.
    function Dot(const AQuaternion: TQuaternion): VectorFloat;

    // Applies spherical linear interpolation between current and given quaternions.
    //   @param(Quaternion Destination quaternion to be used in the interpolation)
    //   @param(Theta The mixture of the two quaternions with range of [0..1].)
    function Slerp(const AQuaternion: TQuaternion; const ATheta: VectorFloat): TQuaternion;

    // Converts quaternion to Euler angles (yaw [Y], pitch [X] and roll [Z]).
    function Angles: TVector3f;

    // Creates 3D quaternion containing rotation around X axis with given angle (in radians).
    class function RotateX(const AAngle: VectorFloat): TQuaternion; static;

    // Creates 3D quaternion containing rotation around Y axis with given angle (in radians).
    class function RotateY(const AAngle: VectorFloat): TQuaternion; static;

    // Creates 3D quaternion containing rotation around Z axis with given angle (in radians).
    class function RotateZ(const AAngle: VectorFloat): TQuaternion; static;

    // Creates 3D quaternion containing rotation around an arbitrary axis with given angle (in radians).
    class function Rotate(const AAxis: TVector3f; const AAngle: VectorFloat): TQuaternion; static;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically Yaw, Pitch and
    // Roll. Each of the components is specified individually.
    class function YawPitchRoll(const AYaw, APitch, ARoll: VectorFloat): TQuaternion; overload; static;

    // Creates 3D rotation matrix based on parameters similar to flight dynamics, specifically Yaw, Pitch and
    // Roll. The components are taken from the specified vector with Y corresponding to Yaw, X to Pitch and
    // Z to Roll.
    class function YawPitchRoll(const AAngles: TVector3f): TQuaternion; overload; static;

    // Creates 3D quaternion setup to perform Object-To-Inertial rotation using the angles specified in
    //  Euler format.
    class function RotateObjectToIntertial(const AHeading, APitch, ABank: VectorFloat): TQuaternion; static;

    // Creates 3D quaternion setup to perform Inertial-To-Object rotation using the angles specified in
    // Euler format.
    class function RotateInertialToObject(const AHeading, APitch, ABank: VectorFloat): TQuaternion; static;
  end;

  // Helper module for TQuaternion.
  TQuaternionHelper = record helper for TQuaternion
  public const
    // Identity quaternion that can be used to specify an object with no rotation.
    Identity: TQuaternion = (X: 0.0; Y: 0.0; Z: 0.0; W: 1.0);
  end;

// Creates a @link(TQuaternion) record using the specified X, Y, Z and W coordinates.
function Quaternion(const AX, AY, AZ, AW: VectorFloat): TQuaternion; inline;

{$ENDREGION}
{$REGION 'TIntRect declarations'}
type
  // Pointer to @link(TIntRect).
  PIntRect = ^TIntRect;

  // General-purpose integer rectangle type defined by top and left margins, width and height.
  TIntRect = record
  private
    function GetRight: VectorInt; inline;
    procedure SetRight(const Value: VectorInt); inline;
    function GetBottom: VectorInt; inline;
    procedure SetBottom(const Value: VectorInt); inline;
    function GetBottomRight: TPoint2i; inline;
    procedure SetBottomRight(const Value: TPoint2i); inline;
  public
    { @exclude } class operator Equal(const ARect1, ARect2: TIntRect): Boolean;
    { @exclude } class operator NotEqual(const ARect1, ARect2: TIntRect): Boolean; inline;

    // Tests whether the rectangle is empty, that is, having width and height of zero or less.
    function Empty: Boolean; inline;

    // Tests whether the given point is inside specified current rectangle.
    function Contains(const APoint: TPoint2i): Boolean; overload; inline;

    // Tests whether the given rectangle is contained within current rectangle.
    function Contains(const ARect: TIntRect): Boolean; overload; inline;

    // Tests whether the given rectangle overlaps current one.
    function Overlaps(const ARect: TIntRect): Boolean; inline;

    // Calculates rectangle that results from intersection between current and the given rectangles.
    function Intersect(const ARect: TIntRect): TIntRect; inline;

    // Calculates rectangle that results from union between current and the given rectangles.
    function Union(const ARect: TIntRect): TIntRect; inline;

    // Displaces current rectangle by the given offset.
    function Offset(const ADelta: TPoint2i): TIntRect; overload; inline;

    // Displaces current rectangle by each of the given offset values.
    function Offset(const ADeltaX, ADeltaY: VectorInt): TIntRect; overload; inline;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADelta: TPoint2i): TIntRect; overload; inline;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADeltaX, ADeltaY: VectorInt): TIntRect; overload;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADelta: VectorInt): TIntRect; overload; inline;

    // Takes source and destination sizes, source rectangle and destination position, then applies clipping
    // to ensure that final rectangle stays within valid boundaries of both source and destination sizes.
    class function ClipCoords(const ASourceSize, ADestSize: TPoint2i; var ASourceRect: TIntRect;
      var ADestPos: TPoint2i): Boolean; static;

    // Right (non-inclusive) margin of the rectangle.
    property Right: VectorInt read GetRight write SetRight;

    // Bottom (non-inclusive) margin of the rectangle.
    property Bottom: VectorInt read GetBottom write SetBottom;

    // Bottom/right (non-inclusive) corner of the rectangle.
    property BottomRight: TPoint2i read GetBottomRight write SetBottomRight;

    case Integer of
      0: (// Left position of the rectangle.
          Left: VectorInt;
          // Top position of the rectangle.
          Top: VectorInt;
          // Width of the rectangle.
          Width: VectorInt;
          // Height of the rectangle.
          Height: VectorInt;);
      1: (// Top/left corner (position) of the rectangle.
          TopLeft: TPoint2i;
          // Size of the rectangle.
          Size: TPoint2i;);
      2: // Individual values represented as an array.
        (Values: array[0..3] of VectorInt);
  end;

  // Helper module for TIntRect.
  TIntRectHelper = record helper for TIntRect
  public const
    // Zero (empty) rectangle with integer coordinates.
    Zero: TIntRect = (Left: 0; Top: 0; Width: 0; Height: 0);
  end;

// Creates rectangle based on top/left position, width and height.
function IntRect(const ALeft, ATop, AWidth, AHeight: VectorInt): TIntRect; overload;

// Creates rectangle based on top/left position and size.
function IntRect(const AOrigin, ASize: TPoint2i): TIntRect; overload;

// Creates rectangle based on individual margin bounds.
function IntRectBDS(const ALeft, ATop, ARight, ABottom: VectorInt): TIntRect; overload;

// Creates rectangle based on top/left and bottom/right margin bounds.
function IntRectBDS(const ATopLeft, ABottomRight: TPoint2i): TIntRect; overload;

{$ENDREGION}
{$REGION 'TFloatRect declarations'}

type
  // Pointer to @link(TFloatRect).
  PFloatRect = ^TFloatRect;

  // General-purpose floating-point rectangle type defined by top and left margins, width and height.
  TFloatRect = record
  private
    function GetRight: VectorFloat; inline;
    procedure SetRight(const Value: VectorFloat); inline;
    function GetBottom: VectorFloat; inline;
    procedure SetBottom(const Value: VectorFloat); inline;
    function GetBottomRight: TPoint2f; inline;
    procedure SetBottomRight(const Value: TPoint2f); inline;
  public
    { @exclude } class operator Implicit(const ARect: TIntRect): TFloatRect; inline;
    { @exclude } class operator Equal(const ARect1, ARect2: TFloatRect): Boolean;
    { @exclude } class operator NotEqual(const ARect1, ARect2: TFloatRect): Boolean; inline;
    { @exclude } class operator Multiply(const ARect: TFloatRect; const AScale: VectorFloat): TFloatRect;

    // Tests whether the rectangle is empty, that is, having width and height of zero or less.
    function Empty: Boolean;

    // Tests whether the given point is inside specified current rectangle.
    function Contains(const APoint: TPoint2f): Boolean; overload;

    // Tests whether the given rectangle is contained within current rectangle.
    function Contains(const ARect: TFloatRect): Boolean; overload; inline;

    // Tests whether the given rectangle overlaps current one.
    function Overlaps(const ARect: TFloatRect): Boolean;

    // Calculates rectangle that results from intersection between current and the given rectangles.
    function Intersect(const ARect: TFloatRect): TFloatRect;

    // Calculates rectangle that results from union between current and the given rectangles.
    function Union(const ARect: TFloatRect): TFloatRect;

    // Displaces current rectangle by the given offset.
    function Offset(const ADelta: TPoint2f): TFloatRect; overload;

    // Displaces current rectangle by each of the given offset values.
    function Offset(const ADeltaX, ADeltaY: VectorFloat): TFloatRect; overload; inline;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADelta: TPoint2f): TFloatRect; overload;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADeltaX, ADeltaY: VectorFloat): TFloatRect; overload; inline;

    // Returns rectangle with left and top decremented, while right and bottom incremented by given offset.
    function Inflate(const ADelta: VectorFloat): TFloatRect; overload; inline;

    // Converts floating-point rectangle to integer rectangle that completely encompasses current rectangle.
    function InnerBounds: TIntRect;

    // Converts floating-point rectangle to integer rectangle that is completely contained within.
    function OuterBounds: TIntRect;

    // Takes source and destination sizes, source and destination rectangles, then applies clipping to ensure
    // that final rectangle stays within valid boundaries of both source and destination sizes.
    class function ClipCoords(const ASourceSize, ADestSize: TPoint2f; var ASourceRect,
      ADestRect: TFloatRect): Boolean; static;

    // Right (non-inclusive) margin of the rectangle.
    property Right: VectorFloat read GetRight write SetRight;

    // Bottom (non-inclusive) margin of the rectangle.
    property Bottom: VectorFloat read GetBottom write SetBottom;

    // Bottom/right (non-inclusive) corner of the rectangle.
    property BottomRight: TPoint2f read GetBottomRight write SetBottomRight;

    case Integer of
      0: (// Left position of the rectangle.
          Left: VectorFloat;
          // Top position of the rectangle.
          Top: VectorFloat;
          // Width of the rectangle.
          Width: VectorFloat;
          // Height of the rectangle.
          Height: VectorFloat;);
      1: (// Top/left corner (position) of the rectangle.
          TopLeft: TPoint2f;
          // Size of the rectangle.
          Size: TPoint2f;);
      2: // Individual values represented as an array.
         (Values: array[0..3] of VectorFloat);
  end;

  // Helper module for TFloatRect.
  TFloatRectHelper = record helper for TFloatRect
  public const
    // Zero (empty) rectangle with floating-point coordinates.
    Zero: TFloatRect = (Left: 0.0; Top: 0.0; Width: 0.0; Height: 0.0);

    // Unity rectangle with floating-point coordinates.
    Unity: TFloatRect = (Left: 0.0; Top: 0.0; Width: 1.0; Height: 1.0);
  end;

// Creates rectangle based on top/left position, width and height.
function FloatRect(const ALeft, ATop, AWidth, AHeight: VectorFloat): TFloatRect; overload;

// Creates rectangle based on top/left position and size.
function FloatRect(const AOrigin, ASize: TPoint2f): TFloatRect; overload;

// Creates rectangle based on individual margin bounds.
function FloatRectBDS(const ALeft, ATop, ARight, ABottom: VectorFloat): TFloatRect; overload;

// Creates rectangle based on top/left and bottom/right margin bounds.
function FloatRectBDS(const ATopLeft, ABottomRight: TPoint2f): TFloatRect; overload;

{$ENDREGION}
{$REGION 'TQuad declarations'}

type
  // Pointer to @link(TQuad).
  PQuad = ^TQuad;

  // Special floating-point quadrilateral defined by four vertices starting from top/left in clockwise order.
  // This is typically used for rendering color filled and textured quads.
  TQuad = record
    { @exclude } class operator Equal(const ARect1, ARect2: TQuad): Boolean;
    { @exclude } class operator NotEqual(const ARect1, ARect2: TQuad): Boolean; inline;

    // Rescales vertices of the given quadrilateral by provided coefficient, optionally centering them around
    // zero origin.
    function Scale(const AScale: VectorFloat; const ACentered: Boolean = True): TQuad;

    // Creates quadrilateral from another quadrilateral but having left vertices exchanged with the right
    // ones, effectively mirroring it horizontally.
    function Mirror: TQuad;

    // Creates quadrilateral from another quadrilateral but having top vertices exchanged with the bottom
    // ones, effectively flipping it vertically.
    function Flip: TQuad;

    // Transforms (multiplies) vertices of given quadrilateral by the specified matrix.
    function Transform(const AMatrix: TMatrix3x2f): TQuad;

    // Displaces vertices of given quadrilateral by the specified offset.
    function Offset(const ADelta: TPoint2f): TQuad; overload;

    // Displaces vertices of given quadrilateral by the specified displacement values.
    function Offset(const ADeltaX, ADeltaY: VectorFloat): TQuad; overload; inline;

    // Tests whether the point is inside current quadrilateral.
    function Contains(const APoint: TPoint2f): Boolean;

    // Creates quadrilateral with the specified top left corner and the given dimensions, which are scaled by
    // the provided coefficient.
    class function Scaled(const ALeft, ATop, AWidth, AHeight, AScale: VectorFloat;
      const ACentered: Boolean = True): TQuad; static;

    // Creates quadrilateral specified by its dimensions. The rectangle is then rotated and scaled around the
    // given middle point (assumed to be inside rectangle's dimensions) and placed in center of the specified
    // origin.
    class function Rotated(const ARotationOrigin, ASize, ARotationCenter: TPoint2f;
      const AAngle: VectorFloat; const AScale: VectorFloat = 1.0): TQuad; overload; static;

    // Creates quadrilateral specified by its dimensions. The rectangle is then rotated and scaled around its
    // center and placed at the specified origin.
    class function Rotated(const ARotationOrigin, ASize: TPoint2f; const AAngle: VectorFloat;
      const AScale: VectorFloat = 1.0): TQuad; overload; static; inline;

    // Creates quadrilateral specified by top-left corner and size. The rectangle is then rotated and scaled
    // around the specified middle point (assumed to be inside rectangle's dimensions) and placed in the
    // center of the specified origin. The difference between this method and @link(Rotated) is that the
    // rotation does not preserve centering of the rectangle in case where middle point is not actually
    // located in the middle.
    class function RotatedTL(const ATopLeft, ASize, ARotationCenter: TPoint2f; const AAngle: VectorFloat;
      const AScale: VectorFloat = 1.0): TQuad; static; inline;

    /// Creates a horizontally skewed quadrilateral, oriented around its origin and with the given
    /// dimensions. A skew angle defines quadrilateral's top/left corner.
    class function SkewedHoriz(const AOrigin, ASize: TPoint2f;
      const AAngle: VectorFloat): TQuad; static; inline;

    /// Creates a vertically skewed quadrilateral, oriented around its origin and with the given
    /// dimensions. A skew angle defines quadrilateral's top/left corner.
    class function SkewedVert(const AOrigin, ASize: TPoint2f;
      const AAngle: VectorFloat): TQuad; static; inline;

    case Integer of
      0: (// Top/left vertex position.
          TopLeft: TPoint2f;
          // Top/right vertex position.
          TopRight: TPoint2f;
          // Bottom/right vertex position.
          BottomRight: TPoint2f;
          // Bottom/left vertex position.
          BottomLeft: TPoint2f;);
      1: // Quadrilateral vertices represented as an array. }
         (Values: array[0..3] of TPoint2f);
  end;

  // Helper module for TQuad.
  TQuadHelper = record helper for TQuad
  public const
    // Quadrilateral with all vertices set to zero.
    Zero: TQuad = (TopLeft: (X: 0.0; Y: 0.0); TopRight: (X: 0.0; Y: 0.0);
      BottomRight: (X: 0.0; Y: 0.0); BottomLeft: (X: 0.0; Y: 0.0));

    // Quadrilateral with vertices set at four corners set between (0, 0) and (1, 1).
    Unity: TQuad = (TopLeft: (X: 0.0; Y: 0.0); TopRight: (X: 1.0; Y: 0.0);
      BottomRight: (X: 1.0; Y: 1.0); BottomLeft: (X: 0.0; Y: 1.0));
  end;

// Creates quadrilateral with individually specified vertex coordinates.
function Quad(const ATopLeftX, ATopLeftY, ATopRightX, ATopRightY, ABottomRightX, ABottomRightY,
  ABottomLeftX, ABottomLeftY: VectorFloat): TQuad; overload;

// Creates quadrilateral with individually specified vertices.
function Quad(const ATopLeft, ATopRight, ABottomRight, ABottomLeft: TPoint2f): TQuad; overload;

// Creates quadrilateral rectangle with top/left position, width and height.
function Quad(const ALeft, ATop, AWidth, AHeight: VectorFloat): TQuad; overload;

// Creates quadrilateral rectangle from specified floating-point rectangle.
function Quad(const ARect: TFloatRect): TQuad; overload;

// Creates quadrilateral rectangle from specified integer rectangle.
function Quad(const ARect: TIntRect): TQuad; overload;

{$ENDREGION}
{$REGION 'TMargins declarations'}

type
  // Pointer to @link(TMargins).
  PMargins = ^TMargins;

  // Margins defined by top, left, right and bottom edge paddings.
  TMargins = record
  private
    function GetHorizontal: VectorFloat; inline;
    function GetVertical: VectorFloat; inline;
  public
    { @exclude } class operator Equal(const AMargins1, AMargins2: TMargins): Boolean;
    { @exclude } class operator NotEqual(const AMargins1, AMargins2: TMargins): Boolean; inline;

    // A sum of left and right paddings.
    property Horizontal: VectorFloat read GetHorizontal;

    // A sum of top and bottom paddings.
    property Vertical: VectorFloat read GetVertical;

    // Tests whether the margins are empty, that is, having all paddings set to zero.
    function Empty: Boolean;

    case Integer of
      0: (// Left edge padding.
          Left: VectorFloat;
          // Top edge padding.
          Top: VectorFloat;
          // Right edge padding.
          Right: VectorFloat;
          // Bottom edge padding.
          Bottom: VectorFloat;);
      1: (// Top/left edge padding.
          TopLeft: TPoint2f;
          // Bottom/right edge padding.
          BottomRight: TPoint2f;);
  end;

  // Helper module for TMargins.
  TMarginsHelper = record helper for TMargins
  public const
    // Zero (empty) padding values.
    Zero: TMargins = (Left: 0.0; Top: 0.0; Right: 0.0; Bottom: 0.0);
  end;

// Creates margins by specifying its paddings individually.
function Margins(const ALeft, ATop, ARight, ABottom: VectorFloat): TMargins; overload;

// Creates margins by specifying horizontal and vertical paddings.
function Margins(const AHorizontal, AVertical: VectorFloat): TMargins; overload;

// Creates margins from a given rectangle.
function Margins(const ARect: TFloatRect): TMargins; overload;

// Creates margins by specifying its top/left and bottom/right corners.
function Margins(const ATopLeft, ABottomRight: TPoint2f): TMargins; overload;

// Creates margins by specifying same padding for all edges.
function Margins(const APadding: VectorFloat): TMargins; overload;

{$ENDREGION}
{$REGION 'Device Types and Functions'}

type
  // One or more device attribute flags combined (ORed) together.
  TDeviceAttributes = Cardinal;

  // Legacy feature bits that define older hardware features.
  TDeviceLegacyBits = Cardinal;

  // Pointer to @link(TRenderingState).
  PRenderingState = ^TRenderingState;

  // Parameters that define Depth, Stencil, Rasterizer and Blending operations.
  TRenderingState = record
  public type
    // State flags that can be combined together to form a rendering operation state.
    State = record const
      // Depth testing should be performed. If this state is not present, depth writing is also disabled.
      DepthTest = $0001;

      // Depth values should be written to Depth Buffer.
      DepthWrite = $0002;

      // Stencil testing should be performed. If this state is not present, no other stencil operation will
      // occur.
      StencilTest = $0004;

      // Geometry should be clipped by the corresponding depth limits.
      DepthClip = $0008;

      // Geometry should be clipped by the corresponding scissor rectangles.
      ScissorClip = $0010;

      // Primitives should be rendered in a wireframe mode.
      Wireframe = $0020;

      // Triangles having vertices in clockwise order would be culled. If this flag is not set, then
      // triangles having vertices in counter-clockwise order would be culled.
      CullClockwise = $0040;

      // Geometry should be rendered with multisampling.
      Multisampling = $0080;

      // Standard 3D lines should be rendered with antialiasing.
      LineAntialias = $0100;

      // Writing to color buffer should be performed.
      ColorWrite = $0200;

      // Blending operation should be performed.
      BlendEnable = $0400;

      // Alpha channel should be converted to coverage samples.
      AlphaToCoverage = $0800;

      // Cube Maps should be sampled from neighbor faces at seams.
      CubeMapSeamless = $1000;

      // Enables fragment shader execution for each individual MSAA sample.
      // This has only effect on OpenGL (ES) based implementations.
      PerSampleShading = $2000;
    end;

    // Stencil state that is independent for each front and back facing.
    TStencilState = record
      // Stencil test function.
      Func: TComparisonFunc;

      // Action to take when stencil test fails.
      FailOp: TStencilOp;

      // Action to take when stencil test passes but depth test fails.
      DepthFailOp: TStencilOp;

      // Action to take when both stencil and depth tests pass, or when stencil test passes and either there
      // is no depth buffer or depth testing is disabled.
      DepthPassOp: TStencilOp;
    end;

    // Blending parameters that are specific to a certain component, or a portion of color.
    TBlend = record
      // Blending factor used for the source component.
      Source: TBlendFactor;

      // Blending factor used for the destination component.
      Dest: TBlendFactor;

      // Blending operation used between source and destination components.
      Op: TBlendOp;
    end;

  public
    // Combination of state flags that define rendering operation.
    States: Cardinal;

    // How triangle faces should be culled.
    CullFace: TTriangleFace;

    // Depth buffer comparison function.
    DepthFunc: TComparisonFunc;

    // Constant offset added to depth to alleviate z-fighting issues.
    DepthBias: Single;

    // Slope coefficient used for relative depth when calculating depth bias.
    SlopeDepthBias: Single;

    // Value to clamp depth bias, preventing artifacts on very high slopes.
    ClampDepthBias: Single;

    // Reference value for the stencil test.
    StencilRefValue: Byte;

    // Mask that is applied to reference value and stored value after the test.
    StencilRefMask: Byte;

    // Mask that is used to enable or disable writing of individual bits to stencil buffer.
    StencilWriteMask: Byte;

    // Custom alpha-blending constant.
    BlendConstant: TFloatColor;

    // Stencil function and operation that are performed for front-facing triangles.
    StencilFront: TStencilState;

    // Stencil function and operation that are performed for back-facing triangles.
    StencilBack: TStencilState;

    // Blending parameters that should be applied to color portion of components.
    BlendColor: TBlend;

    // Blending parameters that should be applied to alpha portion of components.
    BlendAlpha: TBlend;

    // Creates default rendering state.
    class function Default: TRenderingState; static;
  end;

type
  // Device attributes that define its behavior.
  TDeviceAttribute = record const
    // Debug-type context that helps to resolve issues in rendering code.
    Debug = $01;

    // Software-based solution is used for rendering instead of GPU acceleration.
    Software = $02;

    // Compatibility device that is meant for older technologies.
    Legacy = $04;

    // Device should only enable extensions determined by their appropriate level
    // (deviceAttributeExtensions), even if a higher core version is available that inherently supports
    // higher level extensions.
    LimitedExtensions = $80;
  end;

  // Device behavior attributes that define the rendering code path.
  TDeviceBehavior = record const
    // Graphics hardware supports executing fragment shader per each MSAA sample.
    PerSampleShading = $00000001;

    // 3D clip space uses negative values for depth in range of [-1, 1] instead of [0, 1].
    // This typically occurs on OpenGL (ES) backend that do not support clip control functions.
    DepthClipNegative = $00000002;

    // Compute shader support is available.
    Compute = $00000004;

    // Tessellation shader support is available.
    Tessellation = $00000008;

    /// Direct3D 11: Variable Rate Refresh (VRR) display support is available.
    VariableRateRefresh = $00000100;

    /// OpenGL: ARB_post_depth_coverage, Direct3D 11: PostZCoverageEnable through NvAPI.
    PostDepthCoverage = $00000200;

    // OpenGL / Intel HD Graphics bug workaround: always unbind buffers before updating their contents.
    ForceBufferUnbind = $00001000;

    // Direct3D 11 / Parallels bug workaround: clear depth buffer with value lower than one.
    DepthClearBadPrecision = $00004000;

    // Direct3D 11 / VMware bug workaround: avoid signed normalized integer formats.
    SignedNormIntFormatBugged = $00008000;
  end;

  // Cumulative memory barrier bit flags.
  TDeviceBarrier = record const
    // Shader data sourced from buffers after the barrier will reflect data written by shaders prior
    // to the barrier.
    ShaderBuffer = $00000001;

    // Texture fetches from shaders, including fetches from typed buffers, after the barrier will reflect
    // data written by shaders prior to the barrier.
    TextureFetch = $00000002;

    // Memory accesses using shader image load, store, and atomic built-in functions issued after the
    // barrier will reflect data written by shaders prior to the barrier. Additionally, image stores and
    // atomics issued after the barrier will not execute until all memory accesses (e.g., loads, stores,
    // texture fetches, vertex fetches) initiated prior to the barrier complete.
    ShaderImageAccess = $00000004;

    // Writes to a texture by calling Update and Copy functions, and reads via Retrieve after the barrier
    // will reflect data written by shaders prior to the barrier. Additionally, texture writes from these
    // commands issued after the barrier will not execute until all shader writes initiated prior to the
    // barrier complete.
    TextureUpdate = $00000008;

    // Buffer reads or writes bu calling Update and Copy functions after the barrier will reflect data
    // written by shaders prior to the barrier. Additionally, writes via these commands issued after the
    // barrier will wait on the completion of any shader writes to the same memory initiated prior
    // to the barrier.
    BufferUpdate = $00000010;

    // Reads and writes to drawable textures after the barrier will reflect data written by shaders prior
    // to the barrier. Additionally, framebuffer writes issued after the barrier will wait on the
    // completion of all shader writes issued prior to the barrier.
    Drawables = $00000020;

    // Accesses to atomic counters after the barrier will reflect writes prior to the barrier.
    AtomicCounter = $00000040;

    // Accesses to structured buffers after the barrier will reflect writes prior to the barrier.
    Structured = $00000080;
  end;

 // Converts device attribute set to the appropriate 32-bit enumeration.
 function DeviceAttributes(const Attributes: TDeviceAttributes;
   const LimitedExtensions: Integer = -1): Cardinal;

{$ENDREGION}
{$REGION 'Program Types'}

type
  // Pointer to @link(TVertexElement).
  PVertexElement = ^TVertexElement;

  // Structure that describes the layout of a single buffer element.
  TVertexElement = record
  public const
    // Special bit added to @link(TVertexElement.Channel) indicating that the integer values are normalized.
    Normalized = $80000000;
  public
    // Name of the component (variable name in GLSL or semantic name in HLSL, null-terminated).
    Name: array[0..63] of AnsiChar;

    // Data format of each value in this element.
    Format: TElementFormat;

    // Number of values used by this element. If @link(Format) is @link(TElementFormat.Undefined), this
    // indicates number of bytes the element occupies.
    Count: Integer;

    // Channel to which the values of this element will be bound to. The most significant bit of this field
    // has a special purpose, meaning that the data values are normalized (applies only to integers), so the
    // number of channel is stored in first 31 bits.
    Channel: Cardinal;

    // Offset in bytes relative to origin of the first buffer element to the current one.
    Offset: Cardinal;
  end;

  // Pointer to @link(TProgramElement).
  PProgramElement = ^TProgramElement;

  // Structure that describes a single physical element of shader program.
  TProgramElement = record
    // Name of the element (e.g. variable, buffer or texture name, null-terminated).
    Name: array[0..63] of AnsiChar;

    // Type of shader element.
    Element: TShaderElement;

    // Shader to which this element belongs to.
    Shader: TShaderType;

    // A virtual channel index, which will be associated with the appropriate buffer attached to that channel.
    Channel: Cardinal;

    // Index of physical slot to which the element should be bound to. This can be either an actual binding
    // point or a register number.
    Index: Integer;

    // Size of element in bytes.
    Size: Cardinal;
  end;

  // Pointer to @link(TProgramVariable).
  PProgramVariable = ^TProgramVariable;

  // Structure that describes a single (uniform) variable in a shader program.
  TProgramVariable = record
    // Name of the element (e.g. variable, buffer or texture name, null-terminated).
    Name: array[0..63] of AnsiChar;

    // Index that uniquely identifies this variable.
    Index: Integer;

    // Shader to which this element belongs to.
    Shader: TShaderType;

    // Data format of each value in this variable.
    Format: TElementFormat;

    // Number of values used by this variable. If @link(Format) is @link(TElementFormat.Undefined), this
    // indicates number of bytes the element occupies.
    Count: Integer;

    // Size of variable in bytes.
    Size: Cardinal;

    // Offset in a buffer where variable starts.
    Offset: Cardinal;
  end;

  // Pointer to @link(TComputeBindTextureFormat).
  PComputeBindTextureFormat = ^TComputeBindTextureFormat;

  // Compute texture parameters and characteristics.
  TComputeBindTextureFormat = record
    // Channel to which the texture is bound to.
    Channel: Cardinal;

    // Number of mipmap level that should be attached.
    MipLevel: Integer;

    // Layer number from a volume or array texture to bound (-1 means an entire texture should be bound).
    Layer: Integer;

    // Type of access that will be used with the texture.
    Access: TComputeTextureAccess;

    // Format of the texture's pixels.
    Format: TPixelFormat;
  end;

{$ENDREGION}
{$REGION 'Sampler and Texture Types'}

type
  // Pointer to @link(TSamplerState).
  PSamplerState = ^TSamplerState;

  // Sampler parameters that are associated with a particular texture unit.
  TSamplerState = record
    // Minification texture filtering.
    FilterMin: TTextureFilter;

    // Magnification texture filtering.
    FilterMag: TTextureFilter;

    // Mipmapping texture filtering.
    FilterMip: TTextureFilter;

    // Texture coordinate addressing for S, T and R (U, V and W) coordinates.
    Address: array[0..2] of TTextureAddress;

    // Special color constant to be used for TextureAddress::Border addressing mode.
    BorderColor: TFloatColor;

    // Total number of samples to be used with anisotropic filtering.
    Anisotropy: Integer;

    // Minimum level of detail for texture mipmap range.
    MinLOD: Single;

    // Maximum level of detail for texture mipmap range.
    MaxLOD: Single;

    // Offset from the calculated mipmap level.
    BiasLOD: Single;

    // Comparison function for texture values.
    CompareFunc: TComparisonFunc;

    // Indicates whether comparison should be made against a particular reference value or simply fetched.
    CompareToRef: ByteBool;

    // Creates default sampler state.
    class function Default: TSamplerState; static;

    // Creates typical sampler state for shadow mapping.
    class function ShadowMap: TSamplerState; static;
  end;

{$ENDREGION}
{$REGION 'Texture Types'}

type
  // Pointer to @link(TTextureParameters).
  PTextureParameters = ^TTextureParameters;

  // Texture parameters and characteristics.
  TTextureParameters = record
    // Texture width (in pixels).
    Width: Integer;

    // Texture height (in pixels).
    Height: Integer;

    // Number of texture layers in case of Texture Array or depth in case of 3D textures.
    Layers: Integer;

    // Format of the texture's pixels.
    Format: TPixelFormat;

    // Texture type that defines how it is composed.
    TextureType: TTextureType;

    // Attributes that define the characteristics of the texture.
    Attributes: Cardinal;

    // Number of multisamples used in the render target.
    Multisamples: Integer;

    // Type of depth/stencil buffers used in the render target.
    DepthStencil: TPixelFormat;

    // Creates new structure with the given parameters.
    class function Create(const AWidth, AHeight, ALayers: Integer; const AFormat: TPixelFormat;
      const ATextureType: TTextureType = TTextureType.Surface; const AAttributes: Cardinal = 0;
      const AMultisamples: Integer = 0;
      const ADepthStencil: TPixelFormat = TPixelFormat.Unknown): TTextureParameters; static; inline;

    // Returns texture size (in pixels).
    function GetSize: TPoint2i; inline;
  end;

type
  // Attribute that defines special behavior and/or unique characteristics of the texture.
  TTextureAttribute = record const
    // Texture has a full mipmap surface chain attached. MipMapping technique can improve visual quality when
    // the texture is drawn in different sizes, especially in smaller ones.
    MipMapping = $01;

    // Texture contains a special type of surface that can be rendered to. In other words, the texture can be
    // considered a render target (Direct3D) or render buffer object (OpenGL).
    Drawable = $02;

    // Texture is meant for frequent write-only access. Dynamic textures cannot ontain mipmap surface chain
    // attached and cannot be drawable either.
    &Dynamic = $04;

    // Texture has its color components premultiplied by alpha-channel. This implies permanent loss of
    // information as the components are multiplied by alpha value and stored (so, for example, pixels with
    // alpha value of zero permanently lose all color information), however this can improve visual quality on
    // mipmaps with translucent pixels. The parameter is merely a hint for rendering system, it does not change
    // the actual pixels - an action that should be performed separately.
    PremultipliedAlpha = $08;

    // Texture will be used for Compute shader access (Direct3D-only, makes texture UAV-capable).
    Compute = $40;

    // Texture is of "scratch" type. That is, it uses system memory for storage and, when used in context
    // of Direct3D and/or OpenGL, it cannot be used directly.
    Scratch = $8000;
  end;

type
  // One or more texture attribute flags combined together.
  TTextureAttributes = Cardinal;

{$ENDREGION}
{$REGION 'Canvas Types'}

type
  // Pointer to @link(TSignedDistanceField).
  PSignedDistanceField = ^TSignedDistanceField;

  // Signed Distance Field (SDF) parameters.
  TSignedDistanceField = record
    // Type of super-sampling for the field's rendering.
    SuperSampleSDF: TSuperSampleSDF;

    // Distance that was used for generating the field.
    SignedFieldDistance: Single;

    // Color offset in YCH color space for rendering the shape's outline.
    OutlineOffsetSDF: TVector3f;

    // Minimum field distance for the shape's outline.
    OutlineDistanceMinSDF: Single;

    // Maximum field distance for the shape's outline.
    OutlineDistanceMaxSDF: Single;

    // Helper constructor to instantiate signed distance field parameters.
    class function Create(const ASuperSampleSDF: TSuperSampleSDF; const ASignedFieldDistance: Single;
      const AOutlineOffsetSDF: TVector3f; const AOutlineDistanceMinSDF,
      AOutlineDistanceMaxSDF: Single): TSignedDistanceField; static; inline;
  end;

  // Array of @link(TPoint2f) structure.
  TPoint2FArray = array of TPoint2f;

  // Array of @link(TIntColor) structure.
  TIntColorArray = array of TIntColor;

  // Pointer to @link(TCanvasSamplerState).
  PCanvasSamplerState = ^TCanvasSamplerState;

  // Sampler parameters that can be easily configured by the canvas.
  TCanvasSamplerState = record
    // Minification texture filtering.
    FilterMin: TTextureFilter;

    // Magnification texture filtering.
    FilterMag: TTextureFilter;

    // Mipmapping texture filtering.
    FilterMip: TTextureFilter;

    // Horizontal texture coordinate addressing.
    AddressU: TTextureAddress;

    // Vertical texture coordinate addressing.
    AddressV: TTextureAddress;

    // Special color constant to be used for TextureAddress::Border addressing mode.
    BorderColor: TIntColor;

    // Helper constructor to instantiate canvas sampler parameters.
    class function Create(const AFilterMin, AFilterMag: TTextureFilter;
      const AFilterMip: TTextureFilter = TTextureFilter.None;
      const AAddressU: TTextureAddress = TTextureAddress.Wrap;
      const AAddressV: TTextureAddress = TTextureAddress.Wrap;
      const ABorderColor: TIntColor = IntColorBlack): TCanvasSamplerState; static; inline;

    // Returns default canvas sampler parameters.
    class function Default: TCanvasSamplerState; static; inline;
  end;

  // Image region modifier flags that can be combined together.
  TImageAttribute = (
    // Flip region vertically.
    Flip,

    // Mirror region horizontally.
    Mirror,

    // Rotate region by 90 degrees clockwise.
    Rotate);

  // Image region modifier attributes.
  TImageAttributes = set of TImageAttribute;

  // Pointer to @link(TImageRegion).
  PImageRegion = ^TImageRegion;

  // An image region defined by its texture number and bounding rectangle on that texture.
  TImageRegion = record
    // Left position of the region.
    Left: Word;

    // Top position of the region.
    Top: Word;

    // Width of the region.
    Width: Word;

    // Height of the region.
    Height: Word;

    // Texture index.
    Index: Word;

    // Helper constructor to instantiate image region.
    class function Create(const ALeft, ATop, AWidth, AHeight: Integer;
      const AIndex: Integer = 0): TImageRegion; overload; static; inline;

    // Helper constructor to instantiate image region.
    class function Create(const ARect: TIntRect;
      const AIndex: Integer = 0): TImageRegion; overload; static; inline;

    // Tests whether the current region is valid.
    function Valid: Boolean;
  end;

{$ENDREGION}
{$REGION 'Text Rendering Types'}

type
  // Pointer to @link(TTextEntryRect).
  PTextEntryRect = ^TTextEntryRect;

  // Individual text entry that will appear as rendered.
  TTextEntryRect = record
    // Starting character position inside the text (for Unicode, first byte).
    Position: SizeInt;

    // Character's rectangle.
    Rectangle: TFloatRect;
  end;

  // Pointer to @link(TFontEffect).
  PFontEffect = ^TFontEffect;

  // Attributes and characteristics that define how text is rendered on font glyphs.
  TFontEffect = record
    // Brightness of the letter's fill in range of [0, 1], where zero means the letter will appear completely
    // dark and one means that letter will appear completely white.
    FillBrightness: Single;

    // Opacity of the letter's fill in range of [0, 1], where zero means the letter will appear completely
    // transparent (that is, not appear at all) and one means that the letter will appear completely opaque.
    FillOpacity: Single;

    // Type of border that will appear around the letters.
    BorderType: TFontBorder;

    // Thickness of the border that will appear (in pixels).
    BorderThickness: Single;

    // Brightness of the letter's border in range of [0, 1], where zero means the border will appear
    // completely dark and one means that the border will appear completely white.
    BorderBrightness: Single;

    // Opacity of the letter's border in range of [0, 1], where zero means the border will appear completely
    // transparent (that is, not appear at all) and one means that the border will appear completely opaque.
    BorderOpacity: Single;

    // How strong the blur should be applied to letter's shadow.
    ShadowSmoothness: Single;

    // The distance between letter and its shadow (in pixels).
    ShadowDistance: TPoint2f;

    // Brightness of the letter's shadow in range of [0, 1], where zero means the shadow will appear
    // completely dark and one means that the shadow will appear completely white.
    ShadowBrightness: Single;

    // Opacity of the letter's shadow in range of [0, 1], where zero means the shadow will appear completely
    // transparent (that is, not appear at all) and one means that the shadow will appear completely opaque.
    ShadowOpacity: Single;

    // Distance in pixels for generation of Signed Distance Field (SDF) font glyphs. The value must be
    // positive. If this parameter is not zero, then the font is considered SDF.
    SignedFieldDistance: Single;

    { @exclude } class operator Equal(const AEffect1, AEffect2: TFontEffect): Boolean;
    { @exclude } class operator NotEqual(const AEffect1, AEffect2: TFontEffect): Boolean; inline;

    // Creates a new font effect structure with default values.
    class function Default: TFontEffect; static; inline;
  end;

  // Pointer to @link(TTextRenderModifiers).
  PTextRenderModifiers = ^TTextRenderModifiers;

  // Modifier attributes that can be applied to the rendered text.
  TTextRenderModifiers = record
    // Global font scale that is applied to the whole rendered text. A default value of zero means that font
    // scale will not be modified. Using values other than zero or one would likely result in not
    // pixel-perfect text rendering, appearing blurry. However, this can be used for real-time text animations.
    Scale: VectorFloat;

    // Global spacing that will be added horizontally between text letters. This can be used to expand or
    // shrink the text.
    Interleave: VectorFloat;

    // Global spacing that will be added vertically when drawing multiple lines of text.
    VerticalSpace: VectorFloat;

    // Blending effect to use (if set to @link(TBlendingEffect.Undefined), normal effect will be used).
    Effect: TBlendingEffect;
  end;

  // Pointer to @link(TFontParameters).
  PFontParameters = ^TFontParameters;

  // Parameters that define visual characteristics and effects of the font.
  TFontParameters = record
    // Font family (e.g. "Helvetica").
    Family: array[0..127] of AnsiChar;

    // Physical size of the font.
    Size: Single;

    // Font letter weight.
    Weight: TFontWeight;

    // Font letter stretch.
    Stretch: TFontStretch;

    // Font letter slant.
    Slant: TFontSlant;

    // Additional font attributes.
    Attributes: TFontAttributes;

    // Effects applied to font letters.
    Effect: TFontEffect;

    // Creates a new structure with most parameters set to their default values while pre-setting some of them.
    class function Create(const AFamily: Utf8String; const ASize: Single;
      const AWeight: TFontWeight = TFontWeight.Normal; const AStretch: TFontStretch = TFontStretch.Normal;
      const ASlant: TFontSlant = TFontSlant.None;
      const AAttributes: TFontAttributes = []): TFontParameters; static;

    { @exclude } class operator Equal(const AParams1, AParams2: TFontParameters): Boolean;
    { @exclude } class operator NotEqual(const AParams1, AParams2: TFontParameters): Boolean; inline;
  end;

  // 32-bit Unicode character type used for text operations.
  TUnicodeChar = Cardinal;

{$ENDREGION}
{$REGION 'MeshMetaTag Types'}

type
  // Pointer to @link(TMeshMetaTagPortion).
  PMeshMetaTagPortion = ^TMeshMetaTagPortion;

  // A portion of the mesh corresponding to a particular tag.
  TMeshMetaTagPortion = record
    // Starting index of the tag.
    FirstIndex: Integer;

    // Number of indices of the tag.
    IndexCount: Integer;

    // Starting vertex of the tag.
    FirstVertex: Integer;

    // Number of vertices of the tag.
    VertexCount: Integer;

    // Tag's minimum boundaries.
    BoundsMin: TVector3f;

    // Tag's maximum boundaries.
    BoundsMax: TVector3f;
  end;

{$ENDREGION}
{$REGION 'MeshBuffer Types'}

type
  // Pointer to @link(TMeshBufferEntry).
  PMeshBufferEntry = ^TMeshBufferEntry;

  // Calculated values for a single mesh vertex that can be modified by callback.
  TMeshBufferEntry = record
    // Vertex 3D position.
    Position: TVector3f;

    // Vertex 3D normal (normalized).
    Normal: TVector3f;

    // Vertex 3D tangent (normalized).
    Tangent: TVector3f;

    // Vertex 2D texture coordinates.
    TexCoord: TPoint2f;

    // Vertex RGBA color.
    Color: TFloatColor;
  end;

type
  // Mesh voxel rendering function.
  TMeshVoxelVisualizeFunc = procedure(Position, Size: PVector3f; Color: PFloatColor; User: Pointer); cdecl;

{$ENDREGION}
{$REGION 'Mesh Types'}
type
  // Notification event regarding mesh loading or saving process. The notification callback must return 0 to
  // indicate that the process should continue; a non-zero value means that the loading process should be
  // interrupted.
  TMeshLoadSaveFeedback = function(AMessage: PAnsiChar; AProgress: Integer;
    AUser: Pointer): Integer; cdecl;

  // Pointer to @link(TSceneMeshMaterialShading).
  PSceneMeshMaterialShading = ^TSceneMeshMaterialShading;

  // Shading parameters for scene mesh material.
  TSceneMeshMaterialShading = record
    // Ambient color.
    // If one of the values is NaN or Infinity, then ambient color should be deduced from diffuse color.
    Ambient: TFloatColorRGB;

    // Bloom (glare) factor.
    // If this value is NaN or Infinity, then bloom factor is deduced from object material.
    Bloom: Single;

    // Diffuse color (alpha-channel denotes transparency).
    Diffuse: TFloatColor;

    // Specular color.
    Specular: TFloatColorRGB;

    // Specular exponent.
    SpecularExponent: Single;

    // Emissive color.
    Emissive: TFloatColorRGB;

    // Lighting (0: Compatibility, 1: Default, 2: Phong, 3: Minnaert, 4: Cook-Torrance, 5: Oren-Nayar)
    // Compatibility mode ignores ambient color, specular color and specular exponent: ambient color is
    // deduced from diffuse color, whereas specular color and exponent are taken from object material.
    // Default mode considers all colors and factors, but multiplies them with the corresponding values from
    // object material. Starting at value of 2 (Phong) and higher, all colors and factors are considered and
    // they are considered exclusively, overriding corresponding parameters from object material.
    Lighting: Integer;

    // Roughness values for non-Phong lighting models.
    // If a certain value is NaN or Infinity, then that value is deduced from object material.
    Roughness: TVector3f;

    // Reserved value (should be set to zero).
    Reserved: Single;
  end;

  // Pointer to @link(TSceneMeshMaterialRange).
  PSceneMeshMaterialRange = ^TSceneMeshMaterialRange;

  // A range of indices in the mesh that a material applies to.
  TSceneMeshMaterialRange = record
    // Starting index.
    IndexStart: Integer;

    // Number of indices.
    IndexCount: Integer;
  end;

  // Pointer to @link(TSceneMeshLatch).
  PSceneMeshLatch = ^TSceneMeshLatch;

  // A latch in 3D scene mesh, which describes a bone joint connection or a movement waypoint.
  TSceneMeshLatch = record
    // Name of the latch.
    Name: array[0..255] of Char;

    // Position of the latch.
    Position: TVector3f;

    // Type of the latch.
    LatchType: Word;

    // Group of the latch.
    Group: Word;

    // Orientation of the latch.
    Orientation: TQuaternion;
  end;

  // Non-exclusive options passed and/or returned from mesh loading and saving functions.
  TMeshLoadingOption = record const
    // Material diffuse colors should be mapped to vertex colors.
    MaterialVertexColor = $01;

    // Assume "MaterialVertexColor" option only when the model would reasonably look the same as if it would
    // have been rendered with its materials. This requires that all model materials do not use texturing,
    // have no emissive color, no lighting technique, no bloom factor specified or have these at their
    // default values.
    MaterialVertexColorPreferred = $02;

    // One or more material texture could not be loaded.
    MaterialTextureMissing = $80;

    // Include vertex normals in the exported file.
    ExportNormals = $100;

    // Include vertex tangents in the exported file.
    ExportTangents = $200;

    // Include vertex texture coordinates in the exported file.
    ExportTextureCoordinates = $400;

    // Include vertex colors in the exported file.
    ExportColors = $800;

    // Include vertex weights in the exported file.
    ExportWeights = $1000;

    /// Strip names for geometry and object nodes when saving in OBJ format.
    StripGeometryNames = $2000;
  end;

  // Cumulative options that can be passed to one of "AutoDraw" scene/view functions.
  TAutoDrawOption = record const
    // Unbind the rendering buffers after issuing draw call. This can help resolve racing conditions under
    // buggy drivers (mostly OpenGL) at the expense of reduced performance.
    PostUnbind = $01;

    // Reset scene textures after issuing draw call. For performance reasons, the textures should be reset
    // after rendering all objects.
    ResetTextures = $02;

    // Ignore materials integrated into the mesh if such are present, using object material and/or vertex
    // colors instead. This has no impact on "ObjectSkip___HavingMaterials" options functionality.
    MaterialIgnore = $04;

    // Alpha-channel from the scene mesh materials should be assigned to alpha-channel of the scene
    // material (which normally corresponds to bloom factor). This is typically used when rendering
    // semi-transparent objects.
    MaterialTransparency = $08;

    // Portions of the mesh that have semi-transparent materials should be skipped.
    MaterialSkipTransparent = $10;

    // Portions of the mesh that have opaque materials should be skipped.
    MaterialSkipOpaque = $20;

    // Skips objects that have transparent attribute set.
    ObjectSkipTransparent = $40;

    // Skips objects that do not have transparent attribute set.
    ObjectSkipNonTransparent = $80;

    // Skips objects that have mesh materials present.
    ObjectSkipHavingMaterials = $100;

    // Skips objects that do not have mesh materials present.
    ObjectSkipNotHavingMaterials = $200;

    // Draws objects that have a highlight color assigned.
    ObjectHighlighted = $400;

    // Do not use instancing to render multiple objects simultaneously.
    ObjectDisableInstancing = $800;
  end;

{$ENDREGION}
{$REGION 'Scene Types'}

type
  // Pointer to @link(TSceneLight).
  PSceneLight = ^TSceneLight;

  // Light parameters in 3D scene.
  TSceneLight = record
    // Ambient color.
    AmbientColor: TFloatColorRGB;

    // Range at which light attenuation starts (FLT_MAX means no attenuation should be applied).
    AttenuationStart: Single;

    // Albedo color.
    AlbedoColor: TFloatColorRGB;

    // Range at which light attenuation ends. This must be equal to or higher than \c attenuationStart.
    AttenuationEnd: Single;

    // Specular color.
    SpecularColor: TFloatColorRGB;

    // Power coefficient for specular lighting.
    SpecularExponent: Single;

    /// Position of the light source.
    Position: TVector3f;

    // Intensity of the light.
    Intensity: Single;

    // Direction of the light source (non-zero value indicates a spotlight, zero values means omni-light).
    Direction: TVector3f;

    // Angle at which spotlight starts to fade in range of [0, PI).
    Angle: Single;

    // Angle after which spotlight is completely dark. This should be equal to or higher than \c angle.
    AngleCutoff: Single;

    // Index of the shadow caster associated with the light source.
    // A value of -1 indicates light source does not cast shadows.
    ShadowCaster: Integer;

    // Bitmask that defines what objects are affected by this light.
    // A value of zero means that light affects all objects.
    Bitmask: Cardinal;

    // Reserved, must be set to zero.
    Reserved: Cardinal;

    // Custom payload value.
    Payload: TObjectPayload;

    // Creates default scene light parameters.
    class function Default: TSceneLight; static; inline;
  end;

  // Pointer to @link(TObjectMaterial).
  PObjectMaterial = ^TObjectMaterial;

  // Material that describes a particular object in 3D world.
  TObjectMaterial = record
    // Technique that describes how light is reflected off the object's surface.
    Technique: TTechniqueLighting;

    // Ambient color that is always received by the object regardless the position of light.
    AmbientColor: TFloatColorRGB;

    // Strength of received shadows. A value of zero means no shadows will be received.
    Shadows: Single;

    // Albedo color, where alpha-channel is typically a Bloom Factor.
    AlbedoColor: TFloatColor;

    // Specular color, which is added depending on reflected vector from material's surface.
    SpecularColor: TFloatColorRGB;

    // Specular Exponent that defines how short or wide specular reflection appears.
    SpecularExponent: Single;

    // Emissive color, which is always added before any lights.
    EmissiveColor: TFloatColorRGB;

    // Strength of ambient occlusion. A value of zero means no occlusion will be received.
    Occlusion: Single;

    // Roughness of the object's surface that defines how light is reflected.
    Roughness: TVector3f;

    // A bitmask that defines what lights affect this object.
    // A value of zero means that this object is affected by all lights.
    Bitmask: Cardinal;

    // Strength of Frosted Glass effect when rendering order-independent transparency.
    FrostedGlass: Single;

    // Custom payload value.
    Payload: TObjectPayload;

    // Creates default object material parameters.
    class function Default: TObjectMaterial; static; inline;
  end;

  // Pointer to @link(TShadowParameters).
  PShadowParameters = ^TShadowParameters;

  // Parameters that define how shadows are rendered.
  TShadowParameters = record
    // Primary exponent for ESM and EVSM techniques.
    Exponent1: Single;

    // Secondary exponent for ESM and EVSM techniques.
    Exponent2: Single;

    // Maximum variance delimiter used with VSM and EVSM techniques.
    Variance: Single;

    // Light bleeding sigma for reducing light bleeding artifacts with VSM and EVSM techniques.
    BleedSigma: Single;

    // Bias parameter that is applied to depth derivatives in VSM technique.
    Bias: Single;

    // Sigma value for shadow map's Gaussian Blur.
    BlurSigma: Single;

    // Number of samples for shadow map's Gaussian Blur.
    BlurSamples: Integer;

    // Returns default shadow parameters.
    class function Default: TShadowParameters; static; inline;
  end;

  // Pointer to @link(TToneMappingBloom).
  PToneMappingBloom = ^TToneMappingBloom;

  // Parameters that define behavior of tone-mapping and bloom effects.
  TToneMappingBloom = record
    // Amount to be subtracted from initial HDR colors to determine bloom.
    BloomThreshold: Single;

    // Inverse gamma power to adjust the bloom color after threshold.
    BloomGamma: Single;

    // Grayscale coefficients for red, green and blue components.
    BloomCoefficients: TVector3f;

    // Amount of color to shift into grayscale.
    BloomColorShift: Single;

    // Sigma value of Gaussian Blur for bloom pixels.
    BloomBlurSigma: Single;

    // Number of bloom pixels to sample during Gaussian blur.
    BloomBlurSamples: Integer;

    // Maximum allowed level of white for tone-mapping.
    ToneWhite: Single;

    // Conversion coefficients to calculate Luma from RGB during tone-mapping.
    ToneFactors: TVector3f;

    // Power used to adjust the curve of frosted glass equation.
    FrostedPower: Single;

    // Number of buckets per pixel for OIT technique.
    GlassyBuckets: Single;

    // Returns default tone-mapping and bloom parameters.
    class function Default: TToneMappingBloom; static; inline;
  end;

  // Pointer to @link(TAmbientOcclusionParameters).
  PAmbientOcclusionParameters = ^TAmbientOcclusionParameters;

  // Parameters that define how ambient occlusion is performed.
  TAmbientOcclusionParameters = record
    // Radius of the occlusion.
    Radius: Single;

    // Number of kernel samples.
    Samples: Integer;

    // Linear attenuation.
    Attenuation: Single;

    // Contrast of the occlusion.
    Contrast: Single;

    // Bias value to resolve depth precision issues.
    DepthBias: Single;

    // Bias value to prevent sampling close to tangent surface.
    KernelBias: Single;

    // Sigma coefficient for blur filter.
    BlurSigma: Single;

    // Fall-off coefficient for detecting depth discontinuities in blur filter.
    BlurFallOff: Single;

    // Strenth of the occlusion effect.
    Strength: Single;

    // Returns default ambient occlusion parameters.
    class function Default: TAmbientOcclusionParameters; static; inline;
  end;

  // Pointer to @link(TParallaxMappingParameters).
  PParallaxMappingParameters = ^TParallaxMappingParameters;

  // Parameters that define how parallax mapping is performed.
  TParallaxMappingParameters = record
    // Scale that defines how height value from parallax map translates into 3D depth.
    Scale: Single;

    // Minimum number of samples to read per pixel.
    SamplesMin: Integer;

    // Maximum number of samples to read per pixel.
    SamplesMax: Integer;

    // Occlusion strength applied from Occlusion Map (alpha-channel of Normal Map).
    Occlusion: Single;

    // Returns default parallax mapping parameters.
    class function Default: TParallaxMappingParameters; static; inline;
  end;

  // Pointer to @link(TSelectionHighlightParameters).
  PSelectionHighlightParameters = ^TSelectionHighlightParameters;

  // Parameters that define how selection highlight technique is performed.
  TSelectionHighlightParameters = record
    // Color of the selection glow's outline.
    OutlineColor: TFloatColorRGB;

    // At which position outline begins (values of 1 or higher mean no outline).
    OutlineStart: Single;

    // How intense should the glow appear.
    GlowIntensity: Single;

    // Number of passes for the blur effect.
    BlurPasses: Integer;

    // Offset for the blur effect.
    BlurOffset: TPoint2f;

    // Returns default selection highlight parameters.
    class function Default: TSelectionHighlightParameters; static; inline;
  end;

  // Pointer to @link(TFogParameters).
  PFogParameters = ^TFogParameters;

  // Structure containing spatial fog characteristics.
  TFogParameters = record
    // Fog color.
    Color: TFloatColor;

    // Fog opacity.
    Opacity: Single;

    /// Equation of the plane for ground fog.
    GroundPlane: TVector4f;

    /// Ground fog density factor.
    DensityGround: Single;

    // Returns default spatial fog parameters.
    class function Default: TFogParameters; static; inline;

    case Cardinal of
      0: (// Distance (far plane) for linear fog.
          Distance: Single;
          // Bias (transforms the curve) for linear fog.
          Bias: Single;);
      1: (// In-scattering parameter for exponential and ground fog.
          Scattering: Single;
          // Extinction parameter for exponential and ground fog.
          Extinction: Single;);
  end;

  // Pointer to @link(TOceanWavesParameters).
  POceanWavesParameters = ^TOceanWavesParameters;

  // Parameters that define the appearance of waves simulation.
  TOceanWavesParameters = record
    // Resolution of the simulation textures.
    Resolution: Integer;

    // General direction in which the waves propagate.
    Wind: TPoint2f;

    // Size of the generated waves.
    WaveSize: Single;

    // Choppiness of the generated waves.
    Choppiness: Single;

    // Returns default ocean wave parameters.
    class function Default: TOceanWavesParameters; static; inline;
  end;

  // Pointer to @link(TOceanMaterial).
  POceanMaterial = ^TOceanMaterial;

  // Material that describes ocean water surface in a 3D simulation.
  TOceanMaterial = record
    // Ambient color.
    AmbientColor: TFloatColorRGB;

    // Fresnel Coefficient (reflectance at normal incidence).
    Fresnel: Single;

    // Ambient color that is always received by the ocean regardless the position of light.
    AlbedoColor: TFloatColor;

    // Specular color, which is added depending on reflected vector from ocean's surface.
    SpecularColor: TFloatColorRGB;

    // Specular color, which is added depending on reflected vector from ocean's surface.
    SpecularExponent: Single;

    // Emissive color, which is always added before any lights.
    EmissiveColor: TFloatColorRGB;

    // Extinction of the ocean's surface, which determines its transparency.
    Extinction: Single;

    // Strength of a shadows when projected to the object's surface.
    // A value of zero means shadows are not rendered and/or ignored.
    Shadows: Single;

    // A bitmask that defines what lights affect this object.
    // A value of zero means that this object is affected by all lights.
    Bitmask: Cardinal;

    // Returns default ocean material parameters.
    class function Default: TOceanMaterial; static; inline;
  end;

{$ENDREGION}
{$REGION 'Timer Types'}

type
  // Timer callback function.
  TTimerFunc = procedure(TimerHandle: Pointer; User: Pointer); cdecl;

{$ENDREGION}
{$REGION 'ActorCamera Types'}

type
  // Pointer to @link(TCameraConstraints).
  PCameraConstraints = ^TCameraConstraints;

  // Camera position, rotation and distance constraints. If the difference between min and max constraint
  // on a particular axis is almost zero or less, then no constraint is applied.
  TCameraConstraints = record
    // Minimum position allowed for camera positioning. "w" component is a minimum distance constraint.
    PositionMin: TVector4f;

    // Maximum position allowed for camera positioning. "w" component is a maximum distance constraint.
    PositionMax: TVector4f;

    // Minimum camera rotation angles. "w" component is a minimum angle for Lenticular printing rotation.
    RotationMin: TVector4f;

    // Maximum camera rotation angles. "w" component is a maximum angle for Lenticular printing rotation.
    RotationMax: TVector4f;

    // Builds camera constraints with the given values.
    class function Create(const APositionMin, APositionMax: TVector4f; const ARotationMin,
      ARotationMax: TVector4f): TCameraConstraints; static; inline;
  end;

{$ENDREGION}
{$REGION 'Object Model Types'}

type
  // Data type for storing unique object identification numbers.
  TObjectModelID = SizeUInt;

  // Pointer to @link(TMeshAligns).
  PMeshAligns = ^TMeshAligns;

  // Alignment for all three axes that determine the placement of mesh around its model.
  TMeshAligns = record
    // Alignment for X axis.
    X: TMeshAlign;

    // Alignment for Y axis.
    Y: TMeshAlign;

    // Alignment for Z axis.
    Z: TMeshAlign;

    // Bias value used for volume size adjustment.
    Bias: Single;

    // A helper function to build the structure.
    class function Create(const AX: TMeshAlign = TMeshAlign.Origin;
      const AY: TMeshAlign = TMeshAlign.Positive; const AZ: TMeshAlign = TMeshAlign.Origin;
      const ABias: Single = 0.1): TMeshAligns; static; inline;
  end;

  // Object model comparison function.
  TObjectModelCompareFunc = function(AObject1, AObject2: Pointer; AUser: Pointer): Boolean; cdecl;

{$ENDREGION}
{$REGION 'Application Types'}

type
  // Pointer to @link(TApplicationConfiguration).
  PApplicationConfiguration = ^TApplicationConfiguration;

  // Structure that contains all the parameters necessary to create a new application and its window.
  TApplicationConfiguration = record
  public type
    // Fields specific to Windows platform.
    TWin = record
      // Handle of application's instance.
      HandleInstance: TUntypedHandle;

      // Handle of application's "small" icon.
      HandleIconSmall: TUntypedHandle;

      // Handle of application's "big" icon.
      HandleIconBig: TUntypedHandle;

      // Name of application window class.
      WindowClassName: array[0..31] of AnsiChar;
    end;

    // Fields specific to Linux platform.
    TNx = record
      // Type of rendering used with the application.
      RenderingType: Byte;

      // Number of parameters passed to the application.
      ParameterCount: Integer;

      // Parameter strings that were passed to the application.
      ParameterStrings: ^PAnsiChar;
    end;

    // Platform-specific fields that is required for startup.
    TStartup = record
      case Integer of
        0: (Win: TWin);
        1: (Nx: TNx);
    end;
  public
    // Initial size of the application window.
    Size: TPoint2i;

    // Initial state of application window.
    State: TApplicationWindowState;

    // Initial position of application window. If both X and Y are set to High(Integer), then a default
    // position will  be used.
    Position: TPoint2i;

    // Platform-specific fields that is required for startup.
    Startup: TStartup;
  end;

  // Basic application event.
  TEventFunc = procedure(AApplicationHandle: Pointer; AUser: Pointer); cdecl;

  // Basic application event that returns boolean.
  TBoolFunc = function(AApplicationHandle: Pointer; AUser: Pointer): TLibraryBool; cdecl;

  // Application mouse handling event.
  TMouseFunc = procedure(AApplicationHandle: Pointer; AEvent: TMouseEvent; AButton: TMouseButton;
    APosition: PPoint2i; AUser: Pointer); cdecl;

  // Application keyboard handling event.
  TKeyboardFunc = procedure(AApplicationHandle: Pointer; AEvent: TKeyEvent; AVirtualKey: Integer;
    AKeyCode: Word; AUser: Pointer); cdecl;

  // Application status change event.
  TStatusFunc = procedure(AApplicationHandle: Pointer; AEvent: TApplicationEvent; AUser: Pointer); cdecl;

  // Custom application message handling event.
  TEventHookFunc = function(AApplicationHandle: Pointer; AEvent: Pointer;
    AUser: Pointer): TLibraryBool; cdecl;

  // Pointer to @link(TApplicationEvents).
  PApplicationEvents = ^TApplicationEvents;

  // Events that are invoked by the application. Each of these fields is optional, so if a particular event
  // is set to @nil, it will not be invoked and default handler will be used.
  TApplicationEvents = record
    // Event invoked during startup to load application resources. If this function returns  @false,
    // application creation will fail.
    EventCreate: TBoolFunc;

    // Event invoked during application shutdown to release the resources.
    EventDestroy: TEventFunc;

    // Event invoked when application needs to render contents of the window.
    EventRender: TEventFunc;

    // Event invoked when a mouse event has been registered.
    EventMouse: TMouseFunc;

    // Event invoked when a key event has been registered.
    EventKey: TKeyboardFunc;

    // Event invoked when an application status has changed.
    EventStatus: TStatusFunc;

    // Event invoked every time the application is idle.
    EventIdle: TBoolFunc;

    // Event invoked when application window is resized.
    EventResize: TEventFunc;

    // Event hook invoked to process application events.
    EventHook: TEventHookFunc;
  end;

{$ENDREGION}
{$REGION 'Widget Types'}

type
  // Pointer to @link(TWidgetExternalEvent).
  PWidgetExternalEvent = ^TWidgetExternalEvent;

  // Widget's external callback event type.
  TWidgetExternalEvent = function(Widget: TUntypedObjectHandle; Event: PAnsiChar;
    User: Pointer): TLibraryBool; cdecl;

  // One or more attribute flags that define how widget manager behaves.
  TWidgetManagerAttribute = record const
    // Widgets support of composition effects such as glass and blur.
    Composition = $01;
  end;

  // A collection of widget property attribute bitmasks.
  TWidgetPropertyAttribute = record const
    // The property is assigned: that is, it has an accompanying value.
    Assigned = $1000;
  end;

  // Pointer to @link(TWidgetPropertyData).
  PWidgetPropertyData = ^TWidgetPropertyData;

  // A flexible structure for storing property data.
  TWidgetPropertyData = record
    case Integer of
      0: (ValueInteger: Integer);
      1: (ValueInt64: Int64);
      2: (ValueFloat: Single);
      3: (ValueReal: Double);
      4: (ValueBool: ByteBool);
      5: (ValuePoint: TPoint2f);
      6: (ValueVector: TVector3f);
      7: (ValueRect: TFloatRect);
      8: (ValueMargins: TMargins);
      9: (ValueColor: TIntColor);
      10: (ValueColorPair: TColorPair);
      11: (ValueColorRect: TColorRect);
      12: (ValueEnum: Byte);
      13: (ValueString: array[0..183] of AnsiChar);
      14: (ValueFont: TFontParameters);
  end;

  // Pointer to @link(TWidgetProperty).
  PWidgetProperty = ^TWidgetProperty;

  // Information about a widget property.
  TWidgetProperty = record
    // Name of the property.
    Name: array[0..255] of AnsiChar;

    // Data type of the property.
    PropType: TWidgetPropertyType;

    // Behavior of the property.
    Behavior: TWidgetPropertyBehavior;

    // Additional attributes of the property.
    Attributes: Word;

    // Location of the property in the list of existing properties. This is an opaque value that loosely
    // determines next property to be iterated. The actual value is temporary and valid only during
    // continuous iteration. Any change to properties invalidates all previous iteration values.
    // A value of zero indicates an invalid or "undefined" location.
    Location: Word;

    // Reserved bits, must be set to zero.
    Reserved: Word;

    // Contents of the property.
    Data: TWidgetPropertyData;
  end;

{$ENDREGION}
{$REGION 'Vector and Model Types'}

type
  // Array of TPathJoint structure.
  TPathJointArray = array of TPathJoint;

  // Pointer to @link(TPathElement).
  PPathElement = ^TPathElement;

  // A single element in path declaration.
  TPathElement = record
    // Creates path element equal to a given floating-point value.
    class function Create(const AValue: Single = 0.0): TPathElement; overload; static; inline;

    // Creates path element being a specific command with an optional length parameter.
    class function Create(const ACommand: Byte;
      const ALength: Word = 0): TPathElement; overload; static; inline;

    case Cardinal of
      0: (// Path command enumeration combined with one of its attribute bits.
          Command: Byte;

          // Field reserved for future use.
          Reserved: Byte;

          // Number of "value" elements that follows this declaration.
          Length: Word);
      1: // A single 32-bit floating-point value of the path coordinates.
         (Value: Single);
  end;

  // An array of path elements.
  TPathElementArray = TArray<TPathElement>;

  // Path commands and attribute bits.
  TPathCommand = record
  public const
    // Close path, connecting previous position to the first position in the path.
    Close: Byte = $00;

    // Move position to the given coordinates.
    // This is followed by a pair of (x, y) coordinates.
    MoveTo: Byte = $01;

    // Draw a line from current position to the given coordinates.
    // This is followed by a pair of (x, y) coordinates.
    LineTo: Byte = $02;

    // Draw a cubic Bezier curve from current position to the given coordinates and with the given control
    // points. This is followed by three pairs of (x, y) coordinates, the first two being control points and
    // last one being the destination position.
    CurveTo: Byte = $03;

    // Draw a quadratic Bezier curve from current position to the given coordinates and with the given
    // control point. This is followed by two pairs of (x, y) coordinates, the first one being a control
    // point and last one being the destination position.
    QuadCurveTo: Byte = $04;

    // Change "flatness" value used for converting Bezier curves into line segments. This is followed by
    // a single value. Default value is "0.25". Note: if "Relative" flag is set, then "length" field is
    // interpreted as a 16-bit floating-point (float16 or "half float"), and it is assumed that no additional
    // values are following this command.
    Flatness: Byte = $1A;

    // Change angle tolerance for Bezier curves to the given value. This is followed by a single value.
    // A value of zero or less means angle tolerance is not checked. Default value is zero. A typical value
    // for this would be something like PI / 7. Note: if "Relative" flag is set, then "length" field is
    // interpreted as a 16-bit floating-point (float16 or "half float"), and it is assumed that no additional
    // values are following this command.
    ToleranceAngle: Byte = $1B;

    // Change cusp limit angle for Bezier curves to the given value. This is followed by a single value.
    // A value of zero or less means cust limit angle is not checked. Default value is zero.
    // Note: if "Relative" flag is set, then "length" field is interpreted as a 16-bit floating-point
    // (float16 or "half float"), and it is assumed that no additional values are following this command.
    LimitCusp: Byte = $1C;

    // A bit flag that indicates that command should take previous control point mirrored around previous
    // position and use it as the first control point for the command that requires it. This only applies
    // to Bezier curves. If this flag is specified, then there will be one less control point following
    // the command declaration (so for "CurveTo", it'll be just one control point, and for "QuadCurveTo"
    // there will be no control points).
    Smooth: Byte = $40;

    // A bit flag that indicates that command is relative to previous position. If this is the first command
    // in the path, then it would be considered as absolute (or relative to zero, which would be the same).
    // This flag has also special meaning when changing tolerances and angles (see above).
    Relative: Byte = $80;
  end;

  // A container and helper module to facilitate creation of path elements.
  TPathBuilder = class
  private type
    TPathElements = TList<TPathElement>;
  private
    FElements: TPathElements;
    FLastCommand: Byte;
    FLastIndex: Integer;
    function GetElements: TPathElementArray; inline;
    procedure PassCommandPoints(const ACommand: Byte; const APoints: array of TPoint2f;
      const ARelative: Boolean);
  public
    // Creates a new instance of path builder module.
    constructor Create;

    // Releases current instance of path builder module.
    destructor Destroy; override;

    // Removes all existing elements.
    procedure Clear;

    // Closes current path.
    procedure ClosePath;

    // Moves position to the given coordinates. This basically means creating a new sub-path.
    procedure MoveTo(const APosition: TPoint2f; const ARelative: Boolean = False);

    // Produces a line between previous position and the given coordinates, adjusting current position
    // accordingly.
    procedure LineTo(const APosition: TPoint2f; const ARelative: Boolean = False);

    // Produces a cubic Bezier curve between previous position, mirrored previous control point and the given
    // control point and coordinates, adjusting current position accordingly.
    procedure CurveTo(const AControlPoint1, AControlPoint2, APosition: TPoint2f;
      const ARelative: Boolean = False);

    // Produces a cubic Bezier curve between previous position, mirrored previous control point and
    // the given control point and coordinates, adjusting current position accordingly.
    procedure SmoothCurveTo(const AControlPoint2, APosition: TPoint2f; const ARelative: Boolean = False);

    // Produces a quadratic Bezier curve between previous position, two control points and the given
    // coordinates, adjusting current position accordingly.
    procedure QuadCurveTo(const AControlPoint, APosition: TPoint2f; const ARelative: Boolean = False);

    // Produces a quadratic Bezier curve between previous position, mirrored previous control point and
    // the given coordinates, adjusting current position accordingly.
    procedure SmoothQuadCurveTo(const APosition: TPoint2f; const ARelative: Boolean = False);

    // Produces a new rectangle path at the given coordinates.
    procedure Rectangle(const ARectangle: TFloatRect; const AClockwise: Boolean = False;
      const ARelative: Boolean = False);

    // Produces a new circle path at the given coordinates.
    procedure Circle(const ACenter: TPoint2f; const ARadius: Single; const AClockwise: Boolean = False;
      const ARelative: Boolean = False);

    // Produces a new rotated quadrilateral (diamond-shaped) path at the given coordinates.
    procedure Diamond(const ACenter: TPoint2f; const ARadius: Single; const AClockwise: Boolean = False;
      const ARelative: Boolean = False);

    /// Produces a new gear-looking shape at the given coordinates.
    procedure Gear(const ACenter: TPoint2f; const ARadius, AAmplitude: Single; const ASteps, ATeeth: Integer;
      const AClockwise: Boolean = False);

    // The resulting array of path elements.
    property Elements: TPathElementArray read GetElements;
  end;

{$ENDREGION}
{$REGION 'Global declarations'}

// Tests whether two floating-points are nearly equal.
function NearlyEqual(const AValue1, AValue2: Single; const AEpsilon: Single = VectorEpsilon): Boolean; inline;

// Tests whether a given float is nearly zero.
function NearlyZero(const AValue: Single; const AEpsilon: Single = VectorEpsilon): Boolean; inline;

// Ensures that the given value stays within specified range limit, clamping it if necessary.
function Saturate(const Value, MinLimit, MaxLimit: VectorFloat): VectorFloat; inline;
{$IFNDEF PASDOC} overload; {$ENDIF}

{$IF SIZEOF(VectorFloat) > 4} { @exclude }
function Saturate(const Value, MinLimit, MaxLimit: Single): Single; overload; inline;
{$IFEND}

// Ensures that the given value stays within specified range limit, clamping it if necessary.
function Saturate(const Value, MinLimit, MaxLimit: VectorInt): VectorInt; inline;
{$IFNDEF PASDOC} overload; {$ENDIF}

{$IF SIZEOF(VectorInt) <> 4} { @exclude }
function Saturate(const Value, MinLimit, MaxLimit: LongInt): LongInt; overload; inline;
{$IFEND}

// Returns @True if the specified value is a power of two or @False otherwise.
function IsPowerOfTwo(const Value: VectorInt): Boolean;

// Returns the least power of two greater or equal to the specified value.
function CeilPowerOfTwo(const Value: VectorInt): VectorInt;

// Returns the greatest power of two lesser or equal to the specified value.
function FloorPowerOfTwo(const Value: VectorInt): VectorInt;

{$ENDREGION}

implementation

uses
{$IFDEF FPC}
  SysUtils,
{$ELSE}
  System.AnsiStrings,
{$ENDIF}
  PXT.Headers;

{$REGION 'Global Functions'}

const
  FLT_MAX = 3.402823E+38;

function NearlyEqual(const AValue1, AValue2, AEpsilon: Single): Boolean;
begin
  Result := (AValue1 = AValue2) or (Abs(AValue1 - AValue2) <= Max(Abs(AValue1), Abs(AValue2)) * AEpsilon);
end;

function NearlyZero(const AValue, AEpsilon: Single): Boolean;
begin
  Result := Abs(AValue) <= AEpsilon;
end;

function Saturate(const Value, MinLimit, MaxLimit: VectorFloat): VectorFloat;
begin
  Result := Value;

  if Result < MinLimit then
    Result := MinLimit;

  if Result > MaxLimit then
    Result := MaxLimit;
end;

{$IF SIZEOF(VectorFloat) > 4}
function Saturate(const Value, MinLimit, MaxLimit: Single): Single;
begin
  Result := Value;

  if Result < MinLimit then
    Result := MinLimit;

  if Result > MaxLimit then
    Result := MaxLimit;
end;
{$IFEND}

function Saturate(const Value, MinLimit, MaxLimit: VectorInt): VectorInt;
begin
  Result := Value;

  if Result < MinLimit then
    Result := MinLimit;

  if Result > MaxLimit then
    Result := MaxLimit;
end;

{$IF SIZEOF(VectorInt) <> 4}
function Saturate(const Value, MinLimit, MaxLimit: LongInt): LongInt; overload;
begin
  Result := Value;

  if Result < MinLimit then
    Result := MinLimit;

  if Result > MaxLimit then
    Result := MaxLimit;
end;
{$IFEND}

function IsPowerOfTwo(const Value: VectorInt): Boolean;
begin
  Result := (Value >= 1) and ((Value and (Value - 1)) = 0);
end;

function CeilPowerOfTwo(const Value: VectorInt): VectorInt;
begin
  Result := Round(Power(2, Ceil(Log2(Value))))
end;

function FloorPowerOfTwo(const Value: VectorInt): VectorInt;
begin
  Result := Round(Power(2, Floor(Log2(Value))))
end;

function DeviceAttributes(const Attributes: TDeviceAttributes; const LimitedExtensions: Integer): Cardinal;
begin
  Result := Byte(Attributes);
  if LimitedExtensions <> -1 then
    Result := (Result or $80) or (Cardinal(Min(LimitedExtensions + 1, 4)) shl 29);
end;

class function TRenderingState.Default: TRenderingState;
begin
  FillChar(Result, SizeOf(TRenderingState), 0);
  Result.States := State.Multisampling or State.ColorWrite;
  Result.DepthFunc := TComparisonFunc.Less;
  Result.StencilRefMask := High(Byte);
  Result.StencilWriteMask := High(Byte);
  Result.BlendConstant := TFloatColor.White;
  Result.BlendColor.Dest := TBlendFactor.Zero;
  Result.BlendAlpha.Dest := TBlendFactor.Zero;
end;

class function TSamplerState.Default: TSamplerState;
begin
  FillChar(Result, SizeOf(TSamplerState), 0);
  Result.FilterMin := TTextureFilter.Nearest;
  Result.FilterMag := TTextureFilter.Nearest;
  Result.CompareFunc := TComparisonFunc.Always;
  Result.BorderColor := TFloatColor.White;
  Result.Anisotropy := 1;
  Result.MinLOD := -1000.0;
  Result.MaxLOD := 100.0;
end;

class function TSamplerState.ShadowMap: TSamplerState;
begin
  FillChar(Result, SizeOf(TSamplerState), 0);
  Result.FilterMin := TTextureFilter.Nearest;
  Result.FilterMag := TTextureFilter.Nearest;
  Result.FilterMip := TTextureFilter.None;
  Result.Address[0] := TTextureAddress.Border;
  Result.Address[1] := TTextureAddress.Border;
  Result.Address[2] := TTextureAddress.Border;
  Result.CompareFunc := TComparisonFunc.Always;
  Result.BorderColor := TFloatColor.White;
  Result.Anisotropy := 1;
  Result.MinLOD := -1000.0;
  Result.MaxLOD := 100.0;
  Result.CompareToRef := False;
end;

class function TSignedDistanceField.Create(const ASuperSampleSDF: TSuperSampleSDF;
  const ASignedFieldDistance: Single; const AOutlineOffsetSDF: TVector3f; const AOutlineDistanceMinSDF,
  AOutlineDistanceMaxSDF: Single): TSignedDistanceField;
begin
  FillChar(Result, SizeOf(TSignedDistanceField), 0);
  Result.SuperSampleSDF := ASuperSampleSDF;
  Result.SignedFieldDistance := ASignedFieldDistance;
  Result.OutlineOffsetSDF := AOutlineOffsetSDF;
  Result.OutlineDistanceMinSDF := AOutlineDistanceMinSDF;
  Result.OutlineDistanceMaxSDF := AOutlineDistanceMaxSDF;
end;

class function TCanvasSamplerState.Create(const AFilterMin, AFilterMag, AFilterMip: TTextureFilter;
  const AAddressU, AAddressV: TTextureAddress; const ABorderColor: TIntColor): TCanvasSamplerState;
begin
  FillChar(Result, SizeOf(TCanvasSamplerState), 0);
  Result.FilterMin := AFilterMin;
  Result.FilterMag := AFilterMag;
  Result.FilterMip := AFilterMip;
  Result.AddressU := AAddressU;
  Result.AddressV := AAddressV;
  Result.BorderColor := ABorderColor;
end;

class function TCanvasSamplerState.Default: TCanvasSamplerState;
begin
  FillChar(Result, SizeOf(TCanvasSamplerState), 0);
  Result.FilterMin := TTextureFilter.Nearest;
  Result.FilterMag := TTextureFilter.Nearest;
  Result.BorderColor := $FFFFFFFF;
end;

class function TImageRegion.Create(const ALeft, ATop, AWidth, AHeight: Integer;
  const AIndex: Integer): TImageRegion;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Width := AWidth;
  Result.Height := AHeight;
  Result.Index := AIndex;
end;

class function TImageRegion.Create(const ARect: TIntRect; const AIndex: Integer): TImageRegion;
begin
  Result.Left := ARect.Left;
  Result.Top := ARect.Top;
  Result.Width := ARect.Width;
  Result.Height := ARect.Height;
  Result.Index := AIndex;
end;

function TImageRegion.Valid: Boolean;
begin
  Result := (Width > 0) and (Height > 0) and (Index <> High(Word));
end;

class function TTextureParameters.Create(const AWidth, AHeight, ALayers: Integer;
  const AFormat: TPixelFormat; const ATextureType: TTextureType; const AAttributes: Cardinal;
  const AMultisamples: Integer; const ADepthStencil: TPixelFormat): TTextureParameters;
begin
  FillChar(Result, SizeOf(TTextureParameters), 0);
  Result.Width := AWidth;
  Result.Height := AHeight;
  Result.Layers := ALayers;
  Result.Format := AFormat;
  Result.TextureType := ATextureType;
  Result.Attributes := AAttributes;
  Result.Multisamples := AMultisamples;
  Result.DepthStencil := ADepthStencil;
end;

function TTextureParameters.GetSize: TPoint2i;
begin
  Result := Point2i(Width, Height);
end;

class operator TFontEffect.Equal(const AEffect1, AEffect2: TFontEffect): Boolean;
begin
  Result :=
    (AEffect1.BorderType = AEffect2.BorderType) and
    NearlyEqual(AEffect1.FillBrightness, AEffect2.FillBrightness) and
    NearlyEqual(AEffect1.FillOpacity, AEffect2.FillOpacity) and
    NearlyEqual(AEffect1.BorderThickness, AEffect2.BorderThickness) and
    NearlyEqual(AEffect1.BorderBrightness, AEffect2.BorderBrightness) and
    NearlyEqual(AEffect1.BorderOpacity, AEffect2.BorderOpacity) and
    NearlyEqual(AEffect1.ShadowSmoothness, AEffect2.ShadowSmoothness) and
    (AEffect1.ShadowDistance = AEffect2.ShadowDistance) and
    NearlyEqual(AEffect1.ShadowBrightness, AEffect2.ShadowBrightness) and
    NearlyEqual(AEffect1.ShadowOpacity, AEffect2.ShadowOpacity) and
    NearlyEqual(AEffect1.SignedFieldDistance, AEffect2.SignedFieldDistance);
end;

class operator TFontEffect.NotEqual(const AEffect1, AEffect2: TFontEffect): Boolean;
begin
  Result := not (AEffect1 = AEffect2);
end;

class function TFontEffect.Default: TFontEffect;
begin
  FillChar(Result, SizeOf(TFontEffect), 0);
  Result.FillBrightness := 1.0;
  Result.FillOpacity := 1.0;
  Result.BorderThickness := 1.0;
  Result.BorderBrightness := 0.25;
  Result.BorderOpacity := 0.75;
  Result.ShadowSmoothness := 3.0;
  Result.ShadowDistance := Point2f(2.0, 2.0);
  Result.ShadowBrightness := 0.15;
end;

class function TFontParameters.Create(const AFamily: Utf8String; const ASize: Single;
  const AWeight: TFontWeight; const AStretch: TFontStretch; const ASlant: TFontSlant;
  const AAttributes: TFontAttributes): TFontParameters;
begin
  FillChar(Result, SizeOf(TFontParameters), 0);
  if Length(AFamily) > 0 then
    Move(AFamily[1], Result.Family, Min(Length(AFamily), 128));
  Result.Size := ASize;
  Result.Weight := AWeight;
  Result.Stretch := AStretch;
  Result.Slant := ASlant;
  Result.Attributes := AAttributes;
  Result.Effect := TFontEffect.Default;
end;

class operator TFontParameters.Equal(const AParams1, AParams2: TFontParameters): Boolean;
begin
  Result :=
    (AParams1.Weight = AParams2.Weight) and
    (AParams1.Stretch = AParams2.Stretch) and
    (AParams1.Slant = AParams2.Slant) and
    (AParams1.Attributes = AParams2.Attributes) and
    (StrLIComp(AParams1.Family, AParams2.Family, SizeOf(AParams1.Family)) = 0) and
    NearlyEqual(AParams1.Size, AParams2.Size) and
    (AParams1.Effect = AParams2.Effect);
end;

class operator TFontParameters.NotEqual(const AParams1, AParams2: TFontParameters): Boolean;
begin
  Result := not (AParams1 = AParams2);
end;

class function TSceneLight.Default: TSceneLight;
begin
  FillChar(Result, SizeOf(TSceneLight), 0);
  Result.AttenuationStart := FLT_MAX;
  Result.AlbedoColor := TFloatColorRGB.White;
  Result.AttenuationEnd := FLT_MAX;
  Result.SpecularColor := TFloatColorRGB.White;
  Result.SpecularExponent := 1.0;
  Result.Intensity := 1.0;
  Result.Angle := Pi;
  Result.AngleCutoff := Pi;
  Result.ShadowCaster := -1;
end;

class function TObjectMaterial.Default: TObjectMaterial;
begin
  FillChar(Result, SizeOf(TObjectMaterial), 0);
  Result.Shadows := 0.5;
  Result.AlbedoColor := TFloatColor.White;
  Result.SpecularExponent := 4.0;
  Result.Shadows := 0.5;
  Result.Roughness := Vector3f(0.8, 1.2, 0.4);
end;

class function TShadowParameters.Default: TShadowParameters;
begin
  FillChar(Result, SizeOf(TShadowParameters), 0);
  Result.Exponent1 := 40.0;
  Result.Exponent2 := 5.0;
  Result.Variance := 1e-7;
  Result.BleedSigma := 0.05;
  Result.Bias := 0.25;
  Result.BlurSigma := 2.0;
  Result.BlurSamples := 9;
end;

class function TToneMappingBloom.Default: TToneMappingBloom;
begin
  FillChar(Result, SizeOf(TToneMappingBloom), 0);
  Result.BloomThreshold := 1.0;
  Result.BloomGamma := 0.45454546;
  Result.BloomCoefficients := Vector3f(0.33333334, 0.33333334, 0.33333334);
  Result.BloomColorShift := 0.75;
  Result.BloomBlurSigma := 3.0;
  Result.BloomBlurSamples := 15;
  Result.ToneWhite := 1.0;
  Result.ToneFactors := Vector3f(0.29889531, 0.58662247, 0.11448222);
  Result.FrostedPower := 2.0;
  Result.GlassyBuckets := 4.0;
end;

class function TAmbientOcclusionParameters.Default: TAmbientOcclusionParameters;
begin
  FillChar(Result, SizeOf(TAmbientOcclusionParameters), 0);
  Result.Radius := 16.0;
  Result.Samples := 64;
  Result.Attenuation := 1.0;
  Result.Contrast := 3.0;
  Result.DepthBias := 0.001;
  Result.KernelBias := 0.02;
  Result.BlurSigma := 2.0;
  Result.BlurFallOff := 1.0;
  Result.Strength := 0.75;
end;

class function TParallaxMappingParameters.Default: TParallaxMappingParameters;
begin
  FillChar(Result, SizeOf(TParallaxMappingParameters), 0);
  Result.Scale := 0.1;
  Result.SamplesMin := 8;
  Result.SamplesMax := 32;
  Result.Occlusion := 0.75;
end;

class function TSelectionHighlightParameters.Default: TSelectionHighlightParameters;
begin
  FillChar(Result, SizeOf(TSelectionHighlightParameters), 0);
  Result.OutlineColor := TFloatColorRGB.Black;
  Result.OutlineStart := 2.0 / 3.0;
  Result.GlowIntensity := 3.0;
  Result.BlurPasses := 2;
  Result.BlurOffset := TPoint2f.Unity;
end;

class function TFogParameters.Default: TFogParameters;
begin
  FillChar(Result, SizeOf(TFogParameters), 0);
  Result.Color := TFloatColor.White;
  Result.Opacity := 1.0;
  Result.GroundPlane := TVector4f.Zero;
  Result.DensityGround := 0.003;
  Result.Distance := 1000.0;
  Result.Scattering := 0.5;
end;

class function TOceanWavesParameters.Default: TOceanWavesParameters;
begin
  FillChar(Result, SizeOf(TOceanWavesParameters), 0);
  Result.Resolution := 512;
  Result.Wind := Point2f(10.0, 10.0);
  Result.WaveSize := 250;
  Result.Choppiness := 1.5;
end;

class function TOceanMaterial.Default: TOceanMaterial;
begin
  FillChar(Result, SizeOf(TOceanMaterial), 0);
  Result.AmbientColor := FloatColorRGB(0.002941, 0.083333, 0.117647);
  Result.Fresnel := 0.5;
  Result.AlbedoColor := FloatColor(0.011765, 0.333333, 0.470588, 1.0);
  Result.SpecularColor := TFloatColorRGB.White;
  Result.SpecularExponent := 10.0;
  Result.EmissiveColor := TFloatColorRGB.Black;
  Result.Extinction := 0.023;
  Result.Shadows := 0.75;
end;

class function TCameraConstraints.Create(const APositionMin, APositionMax: TVector4f; const ARotationMin,
  ARotationMax: TVector4f): TCameraConstraints;
begin
  FillChar(Result, SizeOf(TCameraConstraints), 0);
  Result.PositionMin := APositionMin;
  Result.PositionMax := APositionMax;
  Result.RotationMin := ARotationMin;
  Result.RotationMax := ARotationMax;
end;

class function TMeshAligns.Create(const AX: TMeshAlign; const AY: TMeshAlign; const AZ: TMeshAlign;
  const ABias: Single): TMeshAligns;
begin
  Result.X := AX;
  Result.Y := AY;
  Result.Z := AZ;
  Result.Bias := ABias;
end;

{$ENDREGION}
{$REGION 'TColorPair'}

class operator TColorPair.Implicit(const AColor: TIntColor): TColorPair;
begin
  Result.First := AColor;
  Result.Second := AColor;
end;

class operator TColorPair.Equal(const AColor1, AColor2: TColorPair): Boolean;
begin
  Result := (AColor1.First = AColor2.First) and (AColor1.Second = AColor2.Second);
end;

class operator TColorPair.NotEqual(const AColor1, AColor2: TColorPair): Boolean;
begin
  Result := not (AColor1 = AColor2);
end;

function TColorPair.Empty: Boolean;
begin
  Result := (First = 0) and (Second = 0);
end;

function TColorPair.HasGradient: Boolean;
begin
  Result := First <> Second;
end;

function TColorPair.HasAlpha: Boolean;
begin
  Result := ((First shr 24) > 0) or ((Second shr 24) > 0);
end;

function TColorPair.HasTransparency: Boolean;
begin
  Result := (First shr 24 < 255) or (Second shr 24 < 255);
end;

function ColorPair(const AFirst, ASecond: TIntColor): TColorPair;
begin
  Result.First := AFirst;
  Result.Second := ASecond;
end;

function ColorPair(const AColor: TIntColor): TColorPair;
begin
  Result.First := AColor;
  Result.Second := AColor;
end;

function ColorPairAlpha(const AColorPair: TColorPair; const AAlpha: Integer): TColorPair;
begin
  if AAlpha < 255 then
    Result := ColorPair(MakeColor(AColorPair.First, AAlpha), MakeColor(AColorPair.Second, AAlpha))
  else
    Result := AColorPair;
end;

{$ENDREGION}
{$REGION 'TColorRect'}

class operator TColorRect.Implicit(const AColor: TIntColor): TColorRect;
begin
  Result.TopLeft := AColor;
  Result.TopRight := AColor;
  Result.BottomRight := AColor;
  Result.BottomLeft := AColor;
end;

class operator TColorRect.Equal(const AColor1, AColor2: TColorRect): Boolean;
begin
  Result := (AColor1.TopLeft = AColor2.TopLeft) and (AColor1.TopRight = AColor2.TopRight) and
    (AColor1.BottomRight = AColor2.BottomRight) and (AColor1.BottomLeft = AColor2.BottomLeft);
end;

class operator TColorRect.NotEqual(const AColor1, AColor2: TColorRect): Boolean;
begin
  Result := not (AColor1 = AColor2);
end;

function TColorRect.Empty: Boolean;
begin
  Result := (TopLeft = 0) and (TopRight = 0) and (BottomRight = 0) and (BottomLeft = 0);
end;

function TColorRect.HasGradient: Boolean;
begin
  Result := (TopLeft <> TopRight) or (TopRight <> BottomRight) or (BottomRight <> BottomLeft);
end;

function TColorRect.HasAlpha: Boolean;
begin
  Result := (TopLeft shr 24 > 0) or (TopRight shr 24 > 0) or (BottomRight shr 24 > 0) or (BottomLeft shr 24 > 0);
end;

function TColorRect.HasTransparency: Boolean;
begin
  Result := (TopLeft shr 24 < 255) or (TopRight shr 24 < 255) or (BottomRight shr 24 < 255) or
    (BottomLeft shr 24 < 255);
end;

function ColorRect(const ATopLeft, ATopRight, ABottomRight, ABottomLeft: TIntColor): TColorRect;
begin
  Result.TopLeft := ATopLeft;
  Result.TopRight := ATopRight;
  Result.BottomRight := ABottomRight;
  Result.BottomLeft := ABottomLeft;
end;

function ColorRect(const AColor: TIntColor): TColorRect;
begin
  Result.TopLeft := AColor;
  Result.TopRight := AColor;
  Result.BottomRight := AColor;
  Result.BottomLeft := AColor;
end;

function ColorRectH(const AColor: TColorPair): TColorRect;
begin
  Result.TopLeft := AColor.First;
  Result.TopRight := AColor.Second;
  Result.BottomRight := AColor.Second;
  Result.BottomLeft := AColor.First;
end;

function ColorRectH(const ALeft, ARight: TIntColor): TColorRect;
begin
  Result.TopLeft := ALeft;
  Result.TopRight := ARight;
  Result.BottomRight := ARight;
  Result.BottomLeft := ALeft;
end;

function ColorRectV(const AColor: TColorPair): TColorRect;
begin
  Result.TopLeft := AColor.First;
  Result.TopRight := AColor.First;
  Result.BottomRight := AColor.Second;
  Result.BottomLeft := AColor.Second;
end;

function ColorRectV(const ATop, ABottom: TIntColor): TColorRect;
begin
  Result.TopLeft := ATop;
  Result.TopRight := ATop;
  Result.BottomRight := ABottom;
  Result.BottomLeft := ABottom;
end;

function ColorRectAlpha(const AColorRect: TColorRect; const AAlpha: Integer): TColorRect;
begin
  if AAlpha < 255 then
    Result := ColorRect(MakeColor(AColorRect.TopLeft, AAlpha), MakeColor(AColorRect.TopRight, AAlpha),
      MakeColor(AColorRect.BottomRight, AAlpha), MakeColor(AColorRect.BottomLeft, AAlpha))
  else
    Result := AColorRect;
end;

{$ENDREGION}
{$REGION 'TFloatColorRGB'}

class operator TFloatColorRGB.Add(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := AColor1.Red + AColor2.Red;
  Result.Green := AColor1.Green + AColor2.Green;
  Result.Blue := AColor1.Blue + AColor2.Blue;
end;

class operator TFloatColorRGB.Subtract(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := AColor1.Red - AColor2.Red;
  Result.Green := AColor1.Green - AColor2.Green;
  Result.Blue := AColor1.Blue - AColor2.Blue;
end;

class operator TFloatColorRGB.Multiply(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := AColor1.Red * AColor2.Red;
  Result.Green := AColor1.Green * AColor2.Green;
  Result.Blue := AColor1.Blue * AColor2.Blue;
end;

class operator TFloatColorRGB.Divide(const AColor1, AColor2: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := AColor1.Red / AColor2.Red;
  Result.Green := AColor1.Green / AColor2.Green;
  Result.Blue := AColor1.Blue / AColor2.Blue;
end;

class operator TFloatColorRGB.Multiply(const AColor: TFloatColorRGB; const ATheta: VectorFloat): TFloatColorRGB;
begin
  Result.Red := AColor.Red * ATheta;
  Result.Green := AColor.Green * ATheta;
  Result.Blue := AColor.Blue * ATheta;
end;

class operator TFloatColorRGB.Multiply(const ATheta: VectorFloat; const AColor: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := ATheta * AColor.Red;
  Result.Green := ATheta * AColor.Green;
  Result.Blue := ATheta * AColor.Blue;
end;

class operator TFloatColorRGB.Divide(const AColor: TFloatColorRGB; const ATheta: VectorFloat): TFloatColorRGB;
begin
  Result.Red := AColor.Red / ATheta;
  Result.Green := AColor.Green / ATheta;
  Result.Blue := AColor.Blue / ATheta;
end;

class operator TFloatColorRGB.Equal(const AColor1, AColor2: TFloatColorRGB): Boolean;
begin
  Result := NearlyEqual(AColor1.Red, AColor2.Red) and NearlyEqual(AColor1.Green, AColor2.Green) and
    NearlyEqual(AColor1.Blue, AColor2.Blue);
end;

class operator TFloatColorRGB.NotEqual(const AColor1, AColor2: TFloatColorRGB): Boolean;
begin
  Result := not (AColor1 = AColor2);
end;

function TFloatColorRGB.Defined: Boolean;
begin
  Result :=
    (not IsNan(Red)) and (not IsInfinite(Red)) and
    (not IsNan(Green)) and (not IsInfinite(Green)) and
    (not IsNan(Blue)) and (not IsInfinite(Blue));
end;

function TFloatColorRGB.Invert: TFloatColorRGB;
begin
  Result.Red := 1.0 - Red;
  Result.Green := 1.0 - Green;
  Result.Blue := 1.0 - Blue;
end;

function TFloatColorRGB.Average(const AColor: TFloatColorRGB): TFloatColorRGB;
begin
  Result.Red := (Red + AColor.Red) * 0.5;
  Result.Green := (Green + AColor.Green) * 0.5;
  Result.Blue := (Blue + AColor.Blue) * 0.5;
end;

function TFloatColorRGB.Lerp(const AColor: TFloatColorRGB; const AAlpha: VectorFloat): TFloatColorRGB;
begin
  Result.Red := Red + (AColor.Red - Red) * AAlpha;
  Result.Green := Green + (AColor.Green - Green) * AAlpha;
  Result.Blue := Blue + (AColor.Blue - Blue) * AAlpha;
end;

function TFloatColorRGB.Gray: VectorFloat;
begin
  Result := Red * 0.299 + Green * 0.587 + Blue * 0.114;
end;

function TFloatColorRGB.Saturate: TFloatColorRGB;
begin
  Result.Red := PXT.Types.Saturate(Red, 0.0, 1.0);
  Result.Green := PXT.Types.Saturate(Green, 0.0, 1.0);
  Result.Blue := PXT.Types.Saturate(Blue, 0.0, 1.0);
end;

function TFloatColorRGB.ToInt: TIntColor;
begin
  Result := Cardinal(Round(Red * 255.0)) or (Cardinal(Round(Green * 255.0)) shl 8) or
    (Cardinal(Round(Blue * 255.0)) shl 16) or $FF000000;
end;

function FloatColorRGB(const AColor: TIntColor): TFloatColorRGB;
begin
  Result.Red := (AColor and $FF) / 255.0;
  Result.Green := ((AColor shr 8) and $FF) / 255.0;
  Result.Blue := ((AColor shr 16) and $FF) / 255.0;
end;

function FloatColorRGB(const ARed, AGreen, ABlue: VectorFloat): TFloatColorRGB;
begin
  Result.Red := ARed;
  Result.Green := AGreen;
  Result.Blue := ABlue;
end;

{$ENDREGION}
{$REGION 'TFloatColor'}

class operator TFloatColor.Add(const AColor1, AColor2: TFloatColor): TFloatColor;
begin
  Result.Red := AColor1.Red + AColor2.Red;
  Result.Green := AColor1.Green + AColor2.Green;
  Result.Blue := AColor1.Blue + AColor2.Blue;
  Result.Alpha := AColor1.Alpha + AColor2.Alpha;
end;

class operator TFloatColor.Subtract(const AColor1, AColor2: TFloatColor): TFloatColor;
begin
  Result.Red := AColor1.Red - AColor2.Red;
  Result.Green := AColor1.Green - AColor2.Green;
  Result.Blue := AColor1.Blue - AColor2.Blue;
  Result.Alpha := AColor1.Alpha - AColor2.Alpha;
end;

class operator TFloatColor.Multiply(const AColor1, AColor2: TFloatColor): TFloatColor;
begin
  Result.Red := AColor1.Red * AColor2.Red;
  Result.Green := AColor1.Green * AColor2.Green;
  Result.Blue := AColor1.Blue * AColor2.Blue;
  Result.Alpha := AColor1.Alpha * AColor2.Alpha;
end;

class operator TFloatColor.Divide(const AColor1, AColor2: TFloatColor): TFloatColor;
begin
  Result.Red := AColor1.Red / AColor2.Red;
  Result.Green := AColor1.Green / AColor2.Green;
  Result.Blue := AColor1.Blue / AColor2.Blue;
  Result.Alpha := AColor1.Alpha / AColor2.Alpha;
end;

class operator TFloatColor.Multiply(const AColor: TFloatColor; const ATheta: VectorFloat): TFloatColor;
begin
  Result.Red := AColor.Red * ATheta;
  Result.Green := AColor.Green * ATheta;
  Result.Blue := AColor.Blue * ATheta;
  Result.Alpha := AColor.Alpha * ATheta;
end;

class operator TFloatColor.Multiply(const ATheta: VectorFloat; const AColor: TFloatColor): TFloatColor;
begin
  Result.Red := ATheta * AColor.Red;
  Result.Green := ATheta * AColor.Green;
  Result.Blue := ATheta * AColor.Blue;
  Result.Alpha := ATheta * AColor.Alpha;
end;

class operator TFloatColor.Divide(const AColor: TFloatColor; const ATheta: VectorFloat): TFloatColor;
begin
  Result.Red := AColor.Red / ATheta;
  Result.Green := AColor.Green / ATheta;
  Result.Blue := AColor.Blue / ATheta;
  Result.Alpha := AColor.Alpha / ATheta;
end;

class operator TFloatColor.Equal(const AColor1, AColor2: TFloatColor): Boolean;
begin
  Result := NearlyEqual(AColor1.Red, AColor2.Red) and NearlyEqual(AColor1.Green, AColor2.Green) and
    NearlyEqual(AColor1.Blue, AColor2.Blue) and NearlyEqual(AColor1.Alpha, AColor2.Alpha);
end;

class operator TFloatColor.NotEqual(const AColor1, AColor2: TFloatColor): Boolean;
begin
  Result := not (AColor1 = AColor2);
end;

function TFloatColor.Invert: TFloatColor;
begin
  Result.Red := 1.0 - Red;
  Result.Green := 1.0 - Green;
  Result.Blue := 1.0 - Blue;
  Result.Alpha := 1.0 - Alpha;
end;

function TFloatColor.PremultiplyAlpha: TFloatColor;
begin
  Result.Red := Red * Alpha;
  Result.Green := Green * Alpha;
  Result.Blue := Blue * Alpha;
  Result.Alpha := Alpha;
end;

function TFloatColor.UnpremultiplyAlpha: TFloatColor;
begin
  if Alpha > 0.0 then
  begin
    Result.Red := Red / Alpha;
    Result.Green := Green / Alpha;
    Result.Blue := Blue / Alpha;
    Result.Alpha := Alpha;
  end
  else
    Result := Self;
end;

function TFloatColor.Average(const AColor: TFloatColor): TFloatColor;
begin
  Result.Red := (Red + AColor.Red) * 0.5;
  Result.Green := (Green + AColor.Green) * 0.5;
  Result.Blue := (Blue + AColor.Blue) * 0.5;
  Result.Alpha := (Alpha + AColor.Alpha) * 0.5;
end;

function TFloatColor.Lerp(const AColor: TFloatColor; const AAlpha: VectorFloat): TFloatColor;
begin
  Result.Red := Red + (AColor.Red - Red) * AAlpha;
  Result.Green := Green + (AColor.Green - Green) * AAlpha;
  Result.Blue := Blue + (AColor.Blue - Blue) * AAlpha;
  Result.Alpha := Self.Alpha + (AColor.Alpha - Self.Alpha) * AAlpha;
end;

function TFloatColor.Gray: VectorFloat;
begin
  Result := Red * 0.299 + Green * 0.587 + Blue * 0.114;
end;

function TFloatColor.Saturate: TFloatColor;
begin
  Result.Red := PXT.Types.Saturate(Red, 0.0, 1.0);
  Result.Green := PXT.Types.Saturate(Green, 0.0, 1.0);
  Result.Blue := PXT.Types.Saturate(Blue, 0.0, 1.0);
  Result.Alpha := PXT.Types.Saturate(Alpha, 0.0, 1.0);
end;

function TFloatColor.ToInt: TIntColor;
begin
  Result := Cardinal(Round(Red * 255.0)) or (Cardinal(Round(Green * 255.0)) shl 8) or
    (Cardinal(Round(Blue * 255.0)) shl 16) or (Cardinal(Round(Alpha * 255.0)) shl 24);
end;

function TFloatColor.RGB: TFloatColorRGB;
begin
  Result := FloatColorRGB(Red, Green, Blue);
end;

class operator TFloatColorRect.Implicit(const AColorRect: TColorRect): TFloatColorRect;
begin
  Result := TFloatColorRect.Create(FloatColor(AColorRect.TopLeft), FloatColor(AColorRect.TopRight),
    FloatColor(AColorRect.BottomRight), FloatColor(AColorRect.BottomLeft));
end;

class function TFloatColorRect.Create(const ATopLeft, ATopRight, ABottomRight,
  ABottomLeft: TFloatColor): TFloatColorRect;
begin
  Result.TopLeft := ATopLeft;
  Result.TopRight := ATopRight;
  Result.BottomRight := ABottomRight;
  Result.BottomLeft := ABottomLeft;
end;

function FloatColor(const AColor: TIntColor): TFloatColor;
begin
  Result.Red := (AColor and $FF) / 255.0;
  Result.Green := ((AColor shr 8) and $FF) / 255.0;
  Result.Blue := ((AColor shr 16) and $FF) / 255.0;
  Result.Alpha := ((AColor shr 24) and $FF) / 255.0;
end;

function FloatColor(const ARed, AGreen, ABlue, AAlpha: VectorFloat): TFloatColor;
begin
  Result.Red := ARed;
  Result.Green := AGreen;
  Result.Blue := ABlue;
  Result.Alpha := AAlpha;
end;

function FloatColor(const AColor: TFloatColorRGB; const AAlpha: VectorFloat): TFloatColor;
begin
  Result.Red := AColor.Red;
  Result.Green := AColor.Green;
  Result.Blue := AColor.Blue;
  Result.Alpha := AAlpha;
end;

{$ENDREGION}
{$REGION 'TPoint2i functions'}

class operator TPoint2i.Add(const APoint1, APoint2: TPoint2i): TPoint2i;
begin
  Result.X := APoint1.X + APoint2.X;
  Result.Y := APoint1.Y + APoint2.Y;
end;

class operator TPoint2i.Subtract(const APoint1, APoint2: TPoint2i): TPoint2i;
begin
  Result.X := APoint1.X - APoint2.X;
  Result.Y := APoint1.Y - APoint2.Y;
end;

class operator TPoint2i.Multiply(const APoint1, APoint2: TPoint2i): TPoint2i;
begin
  Result.X := APoint1.X * APoint2.X;
  Result.Y := APoint1.Y * APoint2.Y;
end;

class operator TPoint2i.Divide(const APoint1, APoint2: TPoint2i): TPoint2i;
begin
  Result.X := APoint1.X div APoint2.X;
  Result.Y := APoint1.Y div APoint2.Y;
end;

class operator TPoint2i.Negative(const APoint: TPoint2i): TPoint2i;
begin
  Result.X := -APoint.X;
  Result.Y := -APoint.Y;
end;

class operator TPoint2i.Multiply(const APoint: TPoint2i; const ATheta: VectorInt): TPoint2i;
begin
  Result.X := APoint.X * ATheta;
  Result.Y := APoint.Y * ATheta;
end;

class operator TPoint2i.Multiply(const ATheta: VectorInt; const APoint: TPoint2i): TPoint2i;
begin
  Result.X := ATheta * APoint.X;
  Result.Y := ATheta * APoint.Y;
end;

class operator TPoint2i.Divide(const APoint: TPoint2i; const ATheta: VectorInt): TPoint2i;
begin
  Result.X := APoint.X div ATheta;
  Result.Y := APoint.Y div ATheta;
end;

class operator TPoint2i.Divide(const APoint: TPoint2i; const ATheta: VectorFloat): TPoint2i;
begin
  Result.X := Round(APoint.X / ATheta);
  Result.Y := Round(APoint.Y / ATheta);
end;

class operator TPoint2i.Equal(const APoint1, APoint2: TPoint2i): Boolean;
begin
  Result := (APoint1.X = APoint2.X) and (APoint1.Y = APoint2.Y);
end;

class operator TPoint2i.NotEqual(const APoint1, APoint2: TPoint2i): Boolean;
begin
  Result := (APoint1.X <> APoint2.X) or (APoint1.Y <> APoint2.Y);
end;

function TPoint2i.Swap: TPoint2i;
begin
  Result.X := Y;
  Result.Y := X;
end;

function TPoint2i.Empty: Boolean;
begin
  Result := (X = 0) and (Y = 0);
end;

function TPoint2i.Length: VectorFloat;
begin
  Result := Hypot(X, Y);
end;

function TPoint2i.Distance(const APoint: TPoint2i): VectorFloat;
begin
  Result := (Self - APoint).Length;
end;

function TPoint2i.DistanceSquare(const APoint: TPoint2i): VectorInt;
var
  LDelta: TPoint2i;
begin
  LDelta := Self - APoint;
  Result := LDelta.Dot(LDelta);
end;

function TPoint2i.Angle: VectorFloat;
begin
  if X <> 0 then
    Result := ArcTan2(-Y, X)
  else
    Result := 0;
end;

function TPoint2i.Dot(const APoint: TPoint2i): VectorInt;
begin
  Result := (X * APoint.X) + (Y * APoint.Y);
end;

function TPoint2i.Cross(const APoint: TPoint2i): VectorInt;
begin
  Result := (X * APoint.Y) - (Y * APoint.X);
end;

function TPoint2i.Lerp(const APoint: TPoint2i; const ATheta: VectorFloat): TPoint2i;
begin
  Result.X := Round(X + (APoint.X - X) * ATheta);
  Result.Y := Round(Y + (APoint.Y - Y) * ATheta);
end;

function TPoint2i.InsideTriangle(const AVertex1, AVertex2, AVertex3: TPoint2i): Boolean;
var
  Det: VectorInt;
begin
  Det := (Y - AVertex2.Y) * (AVertex3.X - AVertex2.X) - (X - AVertex2.X) * (AVertex3.Y - AVertex2.Y);

  Result := (Det * ((Y - AVertex1.Y) * (AVertex2.X - AVertex1.X) - (X - AVertex1.X) *
    (AVertex2.Y - AVertex1.Y)) > 0) and (Det * ((Y - AVertex3.Y) * (AVertex1.X - AVertex3.X) -
    (X - AVertex3.X) * (AVertex1.Y - AVertex3.Y)) > 0);
end;

function Point2i(const AX, AY: VectorInt): TPoint2i;
begin
  Result.X := AX;
  Result.Y := AY;
end;

{$ENDREGION}
{$REGION 'TPoint2f functions'}

class operator TPoint2f.Add(const APoint1, APoint2: TPoint2f): TPoint2f;
begin
  Result.X := APoint1.X + APoint2.X;
  Result.Y := APoint1.Y + APoint2.Y;
end;

class operator TPoint2f.Subtract(const APoint1, APoint2: TPoint2f): TPoint2f;
begin
  Result.X := APoint1.X - APoint2.X;
  Result.Y := APoint1.Y - APoint2.Y;
end;

class operator TPoint2f.Multiply(const APoint1, APoint2: TPoint2f): TPoint2f;
begin
  Result.X := APoint1.X * APoint2.X;
  Result.Y := APoint1.Y * APoint2.Y;
end;

class operator TPoint2f.Divide(const APoint1, APoint2: TPoint2f): TPoint2f;
begin
  Result.X := APoint1.X / APoint2.X;
  Result.Y := APoint1.Y / APoint2.Y;
end;

class operator TPoint2f.Negative(const APoint: TPoint2f): TPoint2f;
begin
  Result.X := -APoint.X;
  Result.Y := -APoint.Y;
end;

class operator TPoint2f.Multiply(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f;
begin
  Result.X := APoint.X * ATheta;
  Result.Y := APoint.Y * ATheta;
end;

class operator TPoint2f.Multiply(const ATheta: VectorFloat; const APoint: TPoint2f): TPoint2f;
begin
  Result.X := ATheta * APoint.X;
  Result.Y := ATheta * APoint.Y;
end;

class operator TPoint2f.Divide(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f;
begin
  Result.X := APoint.X / ATheta;
  Result.Y := APoint.Y / ATheta;
end;

class operator TPoint2f.Divide(const APoint: TPoint2f; const ATheta: Integer): TPoint2f;
begin
  Result.X := APoint.X / ATheta;
  Result.Y := APoint.Y / ATheta;
end;

class operator TPoint2f.Implicit(const APoint: TPoint2i): TPoint2f;
begin
  Result.X := APoint.X;
  Result.Y := APoint.Y;
end;

class operator TPoint2f.Equal(const APoint1, APoint2: TPoint2f): Boolean;
begin
  Result := NearlyEqual(APoint1.X, APoint2.X) and NearlyEqual(APoint1.Y, APoint2.Y);
end;

class operator TPoint2f.NotEqual(const APoint1, APoint2: TPoint2f): Boolean;
begin
  Result := not (APoint1 = APoint2);
end;

{$IFDEF POINT_FLOAT_TO_INT_IMPLICIT}
class operator TPoint2f.Implicit(const APoint: TPoint2f): TPoint2i;
begin
  Result.X := Round(APoint.X);
  Result.Y := Round(APoint.Y);
end;
{$ENDIF}

function TPoint2f.Swap: TPoint2f;
begin
  Result.X := Y;
  Result.Y := X;
end;

function TPoint2f.Empty: Boolean;
begin
  Result := (Abs(X) < VectorEpsilon) and (Abs(Y) < VectorEpsilon);
end;

function TPoint2f.Length: VectorFloat;
begin
  Result := Hypot(X, Y);
end;

function TPoint2f.Distance(const APoint: TPoint2f): VectorFloat;
begin
  Result := (Self - APoint).Length;
end;

function TPoint2f.DistanceSquare(const APoint: TPoint2f): VectorFloat;
var
  LDelta: TPoint2f;
begin
  LDelta := Self - APoint;
  Result := LDelta.Dot(LDelta);
end;

function TPoint2f.Angle: VectorFloat;
begin
  Result := ArcTan2(-Y, X);
end;

function TPoint2f.Dot(const APoint: TPoint2f): VectorFloat;
begin
  Result := (X * APoint.X) + (Y * APoint.Y);
end;

function TPoint2f.Cross(const APoint: TPoint2f): VectorFloat;
begin
  Result := (X * APoint.Y) - (Y * APoint.X);
end;

function TPoint2f.Normalize: TPoint2f;
begin
  Result := Self / Length;
end;

function TPoint2f.Lerp(const APoint: TPoint2f; const ATheta: VectorFloat): TPoint2f;
begin
  Result.X := X + (APoint.X - X) * ATheta;
  Result.Y := Y + (APoint.Y - Y) * ATheta;
end;

function TPoint2f.InsideTriangle(const AVertex1, AVertex2, AVertex3: TPoint2f): Boolean;
var
  Det: VectorFloat;
begin
  Det := (Y - AVertex2.Y) * (AVertex3.X - AVertex2.X) - (X - AVertex2.X) * (AVertex3.Y - AVertex2.Y);

  Result := (Det * ((Y - AVertex1.Y) * (AVertex2.X - AVertex1.X) - (X - AVertex1.X) *
    (AVertex2.Y - AVertex1.Y)) > 0.0) and (Det * ((Y - AVertex3.Y) * (AVertex1.X - AVertex3.X) -
    (X - AVertex3.X) * (AVertex1.Y - AVertex3.Y)) > 0.0);
end;

function TPoint2f.ToInt: TPoint2i;
begin
  Result.X := Round(X);
  Result.Y := Round(Y);
end;

function Point2f(const AX, AY: VectorFloat): TPoint2f;
begin
  Result.X := AX;
  Result.Y := AY;
end;

{$ENDREGION}
{$REGION 'TVector3f functions'}

class operator TVector3f.Add(const AVector1, AVector2: TVector3f): TVector3f;
begin
  Result.X := AVector1.X + AVector2.X;
  Result.Y := AVector1.Y + AVector2.Y;
  Result.Z := AVector1.Z + AVector2.Z;
end;

class operator TVector3f.Subtract(const AVector1, AVector2: TVector3f): TVector3f;
begin
  Result.X := AVector1.X - AVector2.X;
  Result.Y := AVector1.Y - AVector2.Y;
  Result.Z := AVector1.Z - AVector2.Z;
end;

class operator TVector3f.Multiply(const AVector1, AVector2: TVector3f): TVector3f;
begin
  Result.X := AVector1.X * AVector2.X;
  Result.Y := AVector1.Y * AVector2.Y;
  Result.Z := AVector1.Z * AVector2.Z;
end;

class operator TVector3f.Divide(const AVector1, AVector2: TVector3f): TVector3f;
begin
  Result.X := AVector1.X / AVector2.X;
  Result.Y := AVector1.Y / AVector2.Y;
  Result.Z := AVector1.Z / AVector2.Z;
end;

class operator TVector3f.Negative(const AVector: TVector3f): TVector3f;
begin
  Result.X := -AVector.X;
  Result.Y := -AVector.Y;
  Result.Z := -AVector.Z;
end;

class operator TVector3f.Multiply(const AVector: TVector3f; const ATheta: VectorFloat): TVector3f;
begin
  Result.X := AVector.X * ATheta;
  Result.Y := AVector.Y * ATheta;
  Result.Z := AVector.Z * ATheta;
end;

class operator TVector3f.Multiply(const ATheta: VectorFloat; const AVector: TVector3f): TVector3f;
begin
  Result.X := ATheta * AVector.X;
  Result.Y := ATheta * AVector.Y;
  Result.Z := ATheta * AVector.Z;
end;

class operator TVector3f.Divide(const AVector: TVector3f; const ATheta: VectorFloat): TVector3f;
var
  InvTheta: VectorFloat;
begin
  InvTheta := 1.0 / ATheta;
  Result.X := AVector.X * InvTheta;
  Result.Y := AVector.Y * InvTheta;
  Result.Z := AVector.Z * InvTheta;
end;

class operator TVector3f.Equal(const AVector1, AVector2: TVector3f): Boolean;
begin
  Result := NearlyEqual(AVector1.X, AVector2.X) and NearlyEqual(AVector1.Y, AVector2.Y) and
    NearlyEqual(AVector1.Z, AVector2.Z);
end;

class operator TVector3f.NotEqual(const AVector1, AVector2: TVector3f): Boolean;
begin
  Result := not (AVector1 = AVector2);
end;

function TVector3f.Empty: Boolean;
begin
  Result := (Abs(X) < VectorEpsilon) and (Abs(Y) < VectorEpsilon) and (Abs(Z) < VectorEpsilon);
end;

function TVector3f.Length: VectorFloat;
begin
  Result := Sqrt(Dot(Self));
end;

function TVector3f.Distance(const AVector: TVector3f): VectorFloat;
begin
  Result := (Self - AVector).Length;
end;

function TVector3f.DistanceSquare(const AVector: TVector3f): VectorFloat;
var
  LDelta: TVector3f;
begin
  LDelta := Self - AVector;
  Result := LDelta.Dot(LDelta);
end;

function TVector3f.Dot(const AVector: TVector3f): VectorFloat;
begin
  Result := (X * AVector.X) + (Y * AVector.Y) + (Z * AVector.Z);
end;

function TVector3f.Cross(const AVector: TVector3f): TVector3f;
begin
  Result.X := (Y * AVector.Z) - (Z * AVector.Y);
  Result.Y := (Z * AVector.X) - (X * AVector.Z);
  Result.Z := (X * AVector.Y) - (Y * AVector.X);
end;

function TVector3f.Angle(const AVector: TVector3f): VectorFloat;
var
  CosValue: VectorFloat;
begin
  CosValue := Dot(AVector) / (Sqrt(Dot(Self) * AVector.Dot(AVector)));
  Result := ArcCos(Saturate(CosValue, -1.0, 1.0));
end;

function TVector3f.Normalize: TVector3f;
var
  InvAmp: VectorFloat;
begin
  InvAmp := 1.0 / Length;
  Result := Self * InvAmp;
end;

function TVector3f.Parallel(const ADirection: TVector3f): TVector3f;
begin
  Result := ADirection * (Dot(ADirection) / Sqr(ADirection.Length));
end;

function TVector3f.Perpendicular(const ADirection: TVector3f): TVector3f;
begin
  Result := Self - Parallel(ADirection);
end;

function TVector3f.Reflect(const ANormal: TVector3f): TVector3f;
begin
  Result := Self - (ANormal * (Dot(ANormal) * 2.0));
end;

function TVector3f.Lerp(const AVector: TVector3f; const ATheta: VectorFloat): TVector3f;
begin
  Result.X := X + (AVector.X - X) * ATheta;
  Result.Y := Y + (AVector.Y - Y) * ATheta;
  Result.Z := Z + (AVector.Z - Z) * ATheta;
end;

function TVector3f.GetXY: TPoint2f;
begin
  Result.X := X;
  Result.Y := Y;
end;

function Vector3f(const AX, AY, AZ: VectorFloat): TVector3f;
begin
  Result.X := AX;
  Result.Y := AY;
  Result.Z := AZ;
end;

function Vector3f(const APoint: TPoint2f; const AZ: VectorFloat): TVector3f; overload;
begin
  Result.X := APoint.X;
  Result.Y := APoint.Y;
  Result.Z := AZ;
end;

{$ENDREGION}
{$REGION 'TVector4f functions'}

class operator TVector4f.Add(const AVector1, AVector2: TVector4f): TVector4f;
begin
  Result.X := AVector1.X + AVector2.X;
  Result.Y := AVector1.Y + AVector2.Y;
  Result.Z := AVector1.Z + AVector2.Z;
  Result.W := AVector1.W + AVector2.W;
end;

class operator TVector4f.Subtract(const AVector1, AVector2: TVector4f): TVector4f;
begin
  Result.X := AVector1.X - AVector2.X;
  Result.Y := AVector1.Y - AVector2.Y;
  Result.Z := AVector1.Z - AVector2.Z;
  Result.W := AVector1.W - AVector2.W;
end;

class operator TVector4f.Multiply(const AVector1, AVector2: TVector4f): TVector4f;
begin
  Result.X := AVector1.X * AVector2.X;
  Result.Y := AVector1.Y * AVector2.Y;
  Result.Z := AVector1.Z * AVector2.Z;
  Result.W := AVector1.W * AVector2.W;
end;

class operator TVector4f.Divide(const AVector1, AVector2: TVector4f): TVector4f;
begin
  Result.X := AVector1.X / AVector2.X;
  Result.Y := AVector1.Y / AVector2.Y;
  Result.Z := AVector1.Z / AVector2.Z;
  Result.W := AVector1.W / AVector2.W;
end;

class operator TVector4f.Negative(const AVector: TVector4f): TVector4f;
begin
  Result.X := -AVector.X;
  Result.Y := -AVector.Y;
  Result.Z := -AVector.Z;
  Result.W := -AVector.W;
end;

class operator TVector4f.Multiply(const AVector: TVector4f; const ATheta: VectorFloat): TVector4f;
begin
  Result.X := AVector.X * ATheta;
  Result.Y := AVector.Y * ATheta;
  Result.Z := AVector.Z * ATheta;
  Result.W := AVector.W * ATheta;
end;

class operator TVector4f.Multiply(const ATheta: VectorFloat; const AVector: TVector4f): TVector4f;
begin
  Result.X := ATheta * AVector.X;
  Result.Y := ATheta * AVector.Y;
  Result.Z := ATheta * AVector.Z;
  Result.W := ATheta * AVector.W;
end;

class operator TVector4f.Divide(const AVector: TVector4f; const ATheta: VectorFloat): TVector4f;
begin
  Result.X := AVector.X / ATheta;
  Result.Y := AVector.Y / ATheta;
  Result.Z := AVector.Z / ATheta;
  Result.W := AVector.W / ATheta;
end;

class operator TVector4f.Equal(const AVector1, AVector2: TVector4f): Boolean;
begin
  Result := NearlyEqual(AVector1.X, AVector2.X) and NearlyEqual(AVector1.Y, AVector2.Y) and
    NearlyEqual(AVector1.Z, AVector2.Z) and NearlyEqual(AVector1.W, AVector2.W);
end;

class operator TVector4f.NotEqual(const AVector1, AVector2: TVector4f): Boolean;
begin
  Result := not (AVector1 = AVector2);
end;

function TVector4f.Lerp(const AVector: TVector4f; const ATheta: VectorFloat): TVector4f;
begin
  Result.X := X + (AVector.X - X) * ATheta;
  Result.Y := Y + (AVector.Y - Y) * ATheta;
  Result.Z := Z + (AVector.Z - Z) * ATheta;
  Result.W := W + (AVector.W - W) * ATheta;
end;

function TVector4f.GetXYZ: TVector3f;
begin
  Result.X := X;
  Result.Y := Y;
  Result.Z := Z;
end;

function TVector4f.ProjectToXYZ: TVector3f;
begin
  Result := GetXYZ * (1.0 / W);
end;

function Vector4f(const AX, AY, AZ, AW: VectorFloat): TVector4f;
begin
  Result.X := AX;
  Result.Y := AY;
  Result.Z := AZ;
  Result.W := AW;
end;

function Vector4f(const AVector: TVector3f; const AW: VectorFloat): TVector4f;
begin
  Result.X := AVector.X;
  Result.Y := AVector.Y;
  Result.Z := AVector.Z;
  Result.W := AW;
end;

function Vector4f(const APoint: TPoint2f; const AZ, AW: VectorFloat): TVector4f;
begin
  Result.X := APoint.X;
  Result.Y := APoint.Y;
  Result.Z := AZ;
  Result.W := AW;
end;

{$ENDREGION}
{$REGION 'TMatrix3f functions'}

class operator TMatrix3x2f.Add(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
var
  I, J: Integer;
begin
  for J := 0 to 2 do
    for I := 0 to 1 do
      Result.Data[J, I] := AMatrix1.Data[J, I] + AMatrix2.Data[J, I];
end;

class operator TMatrix3x2f.Subtract(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
var
  I, J: Integer;
begin
  for J := 0 to 2 do
    for I := 0 to 1 do
      Result.Data[J, I] := AMatrix1.Data[J, I] - AMatrix2.Data[J, I];
end;

class operator TMatrix3x2f.Multiply(const AMatrix1, AMatrix2: TMatrix3x2f): TMatrix3x2f;
begin
  Result.Data[0, 0] := AMatrix1.Data[0, 0] * AMatrix2.Data[0, 0] +
    AMatrix1.Data[0, 1] * AMatrix2.Data[1, 0];
  Result.Data[0, 1] := AMatrix1.Data[0, 0] * AMatrix2.Data[0, 1] +
    AMatrix1.Data[0, 1] * AMatrix2.Data[1, 1];
  Result.Data[1, 0] := AMatrix1.Data[1, 0] * AMatrix2.Data[0, 0] +
    AMatrix1.Data[1, 1] * AMatrix2.Data[1, 0];
  Result.Data[1, 1] := AMatrix1.Data[1, 0] * AMatrix2.Data[0, 1] +
    AMatrix1.Data[1, 1] * AMatrix2.Data[1, 1];
  Result.Data[2, 0] := AMatrix1.Data[2, 0] * AMatrix2.Data[0, 0] +
    AMatrix1.Data[2, 1] * AMatrix2.Data[1, 0] + AMatrix2.Data[2, 0];
  Result.Data[2, 1] := AMatrix1.Data[2, 0] * AMatrix2.Data[0, 1] +
    AMatrix1.Data[2, 1] * AMatrix2.Data[1, 1] + AMatrix2.Data[2, 1];
end;

class operator TMatrix3x2f.Multiply(const AMatrix: TMatrix3x2f; const ATheta: VectorFloat): TMatrix3x2f;
var
  I, J: Integer;
begin
  for J := 0 to 2 do
    for I := 0 to 1 do
      Result.Data[J, I] := AMatrix.Data[J, I] * ATheta;
end;

class operator TMatrix3x2f.Multiply(const ATheta: VectorFloat; const AMatrix: TMatrix3x2f): TMatrix3x2f;
begin
  Result := AMatrix * ATheta;
end;

class operator TMatrix3x2f.Divide(const AMatrix: TMatrix3x2f; const ATheta: VectorFloat): TMatrix3x2f;
var
  I, J: Integer;
begin
  for J := 0 to 2 do
    for I := 0 to 1 do
      Result.Data[J, I] := AMatrix.Data[J, I] / ATheta;
end;

class operator TMatrix3x2f.Multiply(const APoint: TPoint2f; const AMatrix: TMatrix3x2f): TPoint2f;
begin
  Result.X := APoint.X * AMatrix.Data[0, 0] + APoint.Y * AMatrix.Data[1, 0] + AMatrix.Data[2, 0];
  Result.Y := APoint.X * AMatrix.Data[0, 1] + APoint.Y * AMatrix.Data[1, 1] + AMatrix.Data[2, 1];
end;

function TMatrix3x2f.Empty: Boolean;
var
  I, J: Integer;
begin
  for J := 0 to 2 do
    for I := 0 to 1 do
      if Abs(Data[J, I]) > VectorEpsilon then
        Exit(False);

  Result := True;
end;

function TMatrix3x2f.Determinant: VectorFloat;
begin
  Result := Data[0, 0] * Data[1, 1] - Data[1, 0] * Data[0, 1];
end;

function TMatrix3x2f.Inverse: TMatrix3x2f;
var
  LInvDet: VectorFloat;
begin
  LInvDet := 1.0 / Determinant;

  Result.Data[0, 0] := Data[1, 1] * LInvDet;
  Result.Data[0, 1] := -Data[0, 1] * LInvDet;
  Result.Data[1, 0] := -Data[1, 0] * LInvDet;
  Result.Data[1, 1] := Data[0, 0] * LInvDet;
  Result.Data[2, 0] := (Data[1, 0] * Data[2, 1] - Data[2, 0] * Data[1, 1]) * LInvDet;
  Result.Data[2, 1] := (Data[2, 0] * Data[0, 1] - Data[0, 0] * Data[2, 1]) * LInvDet;
end;

function TMatrix3x2f.InverseTranspose: TMatrix3x2f;
var
  LInvDet: VectorFloat;
begin
  LInvDet := 1.0 / Determinant;
  Result := TMatrix3x2f.Zero;

  Result.Data[0, 0] := Data[1, 1] * LInvDet;
  Result.Data[1, 0] := -Data[0, 1] * LInvDet;
  Result.Data[0, 1] := -Data[1, 0] * LInvDet;
  Result.Data[1, 1] := Data[0, 0] * LInvDet;
end;

class function TMatrix3x2f.Scale(const AScale: TPoint2f): TMatrix3x2f;
begin
  Result := TMatrix3x2f.Identity;
  Result.Data[0, 0] := AScale.X;
  Result.Data[1, 1] := AScale.Y;
end;

class function TMatrix3x2f.Scale(const AX, AY: VectorFloat): TMatrix3x2f;
begin
  Result := Scale(Point2f(AX, AY));
end;

class function TMatrix3x2f.Scale(const AScale: VectorFloat): TMatrix3x2f;
begin
  Result := Scale(Point2f(AScale, AScale));
end;

class function TMatrix3x2f.Scale(const ACenter: TPoint2f; const AScale: TPoint2f): TMatrix3x2f;
begin
  Result := TMatrix3x2f.Identity;
  Result.Data[0, 0] := AScale.X;
  Result.Data[1, 1] := AScale.Y;
  Result.Data[2, 0] := ACenter.X * (1.0 - AScale.X);
  Result.Data[2, 1] := ACenter.Y * (1.0 - AScale.Y);
end;

class function TMatrix3x2f.Translate(const AOffset: TPoint2f): TMatrix3x2f;
begin
  Result := TMatrix3x2f.Identity;
  Result.Data[2, 0] := AOffset.X;
  Result.Data[2, 1] := AOffset.Y;
end;

class function TMatrix3x2f.Translate(const AX, AY: VectorFloat): TMatrix3x2f;
begin
  Result := Translate(Point2f(AX, AY));
end;

class function TMatrix3x2f.Rotate(const AAngle: VectorFloat): TMatrix3x2f;
var
  LAngleSin, LAngleCos: VectorFloat;
begin
  SinCos(AAngle, LAngleSin, LAngleCos);

  Result := TMatrix3x2f.Identity;
  Result.Data[0, 0] := LAngleCos;
  Result.Data[0, 1] := LAngleSin;
  Result.Data[1, 0] := -Result.Data[0, 1];
  Result.Data[1, 1] := Result.Data[0, 0];
end;

class function TMatrix3x2f.Rotate(const ACenter: TPoint2f; const AAngle: VectorFloat): TMatrix3x2f;
var
  LAngleSin, LAngleCos: VectorFloat;
begin
  SinCos(AAngle, LAngleSin, LAngleCos);

  Result.Data[0, 0] := LAngleCos;
  Result.Data[0, 1] := LAngleSin;
  Result.Data[1, 0] := -Result.Data[0, 1];
  Result.Data[1, 1] := Result.Data[0, 0];
  Result.Data[2, 0] := ACenter.X * (1.0 - LAngleCos) + ACenter.Y * LAngleSin;
  Result.Data[2, 1] := ACenter.Y * (1.0 - LAngleCos) + ACenter.X * LAngleSin;
end;

class function TMatrix3x2f.Skew(const ACenter: TPoint2f; const AAngles: TPoint2f): TMatrix3x2f;
var
  LAngleTan: TPoint2f;
begin
  LAngleTan := Point2f(Tan(AAngles.X), Tan(AAngles.Y));

  Result := TMatrix3x2f.Identity;
  Result.Data[0, 1] := LAngleTan.Y;
  Result.Data[1, 0] := LAngleTan.X;
  Result.Data[2, 0] := -ACenter.Y * LAngleTan.X;
  Result.Data[2, 1] := -ACenter.X * LAngleTan.Y;
end;

{$ENDREGION}
{$REGION 'TMatrix4f functions'}

class operator TMatrix4f.Add(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
var
  I, J: Integer;
begin
  for J := 0 to 3 do
    for I := 0 to 3 do
      Result.Data[J, I] := AMatrix1.Data[J, I] + AMatrix2.Data[J, I];
end;

class operator TMatrix4f.Subtract(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
var
  I, J: Integer;
begin
  for J := 0 to 3 do
    for I := 0 to 3 do
      Result.Data[J, I] := AMatrix1.Data[J, I] - AMatrix2.Data[J, I];
end;

class operator TMatrix4f.Multiply(const AMatrix1, AMatrix2: TMatrix4f): TMatrix4f;
var
  I, J: Integer;
begin
  for J := 0 to 3 do
    for I := 0 to 3 do
      Result.Data[J, I] := (AMatrix1.Data[J, 0] * AMatrix2.Data[0, I]) + (AMatrix1.Data[J, 1] * AMatrix2.Data[1, I]) +
        (AMatrix1.Data[J, 2] * AMatrix2.Data[2, I]) + (AMatrix1.Data[J, 3] * AMatrix2.Data[3, I]);
end;

class operator TMatrix4f.Multiply(const AMatrix: TMatrix4f; const ATheta: VectorFloat): TMatrix4f;
var
  I, J: Integer;
begin
  for J := 0 to 3 do
    for I := 0 to 3 do
      Result.Data[J, I] := AMatrix.Data[J, I] * ATheta;
end;

class operator TMatrix4f.Multiply(const ATheta: VectorFloat; const AMatrix: TMatrix4f): TMatrix4f;
begin
  Result := AMatrix * ATheta;
end;

class operator TMatrix4f.Divide(const AMatrix: TMatrix4f; const ATheta: VectorFloat): TMatrix4f;
var
  I, J: Integer;
begin
  for J := 0 to 3 do
    for I := 0 to 3 do
      Result.Data[J, I] := AMatrix.Data[J, I] / ATheta;
end;

class operator TMatrix4f.Multiply(const AVector: TVector3f; const AMatrix: TMatrix4f): TVector3f;
begin
  Result.X := AVector.X * AMatrix.Data[0, 0] + AVector.Y * AMatrix.Data[1, 0] + AVector.Z * AMatrix.Data[2, 0] +
    AMatrix.Data[3, 0];
  Result.Y := AVector.X * AMatrix.Data[0, 1] + AVector.Y * AMatrix.Data[1, 1] + AVector.Z * AMatrix.Data[2, 1] +
    AMatrix.Data[3, 1];
  Result.Z := AVector.X * AMatrix.Data[0, 2] + AVector.Y * AMatrix.Data[1, 2] + AVector.Z * AMatrix.Data[2, 2] +
    AMatrix.Data[3, 2];
end;

class operator TMatrix4f.Multiply(const AVector: TVector4f; const AMatrix: TMatrix4f): TVector4f;
begin
  Result.X := AVector.X * AMatrix.Data[0, 0] + AVector.Y * AMatrix.Data[1, 0] + AVector.Z * AMatrix.Data[2, 0] + AVector.W *
    AMatrix.Data[3, 0];
  Result.Y := AVector.X * AMatrix.Data[0, 1] + AVector.Y * AMatrix.Data[1, 1] + AVector.Z * AMatrix.Data[2, 1] + AVector.W *
    AMatrix.Data[3, 1];
  Result.Z:= AVector.X * AMatrix.Data[0, 2] + AVector.Y * AMatrix.Data[1, 2] + AVector.Z * AMatrix.Data[2, 2] + AVector.W *
    AMatrix.Data[3, 2];
  Result.W:= AVector.X * AMatrix.Data[0, 3] + AVector.Y * AMatrix.Data[1, 3] + AVector.Z * AMatrix.Data[2, 3] + AVector.W *
    AMatrix.Data[3, 3];
end;

class function TMatrix4f.DetSub3(const A1, A2, A3, B1, B2, B3, C1, C2, C3: VectorFloat): VectorFloat;
begin
  Result := A1 * (B2 * C3 - B3 * C2) - B1 * (A2 * C3 - A3 * C2) + C1 * (A2 * B3 - A3 * B2);
end;

function TMatrix4f.Determinant: VectorFloat;
begin
  Result := Self.Data[0, 0] * DetSub3(Self.Data[1, 1], Self.Data[2, 1], Self.Data[3, 1], Self.Data[1, 2],
    Self.Data[2, 2], Self.Data[3, 2], Self.Data[1, 3], Self.Data[2, 3], Self.Data[3, 3]) - Self.Data[0, 1] *
    DetSub3(Self.Data[1, 0], Self.Data[2, 0], Self.Data[3, 0], Self.Data[1, 2], Self.Data[2, 2], Self.Data[3, 2],
    Self.Data[1, 3], Self.Data[2, 3], Self.Data[3, 3]) + Self.Data[0, 2] * DetSub3(Self.Data[1, 0], Self.Data[2, 0],
    Self.Data[3, 0], Self.Data[1, 1], Self.Data[2, 1], Self.Data[3, 1], Self.Data[1, 3], Self.Data[2, 3],
    Self.Data[3, 3]) - Self.Data[0, 3] * DetSub3(Self.Data[1, 0], Self.Data[2, 0], Self.Data[3, 0], Self.Data[1, 1],
    Self.Data[2, 1], Self.Data[3, 1], Self.Data[1, 2], Self.Data[2, 2], Self.Data[3, 2]);
end;

function TMatrix4f.EyePos: TVector3f;
begin
  Result.X := -Self.Data[0, 0] * Self.Data[3, 0] - Self.Data[0, 1] * Self.Data[3, 1] - Self.Data[0, 2] *
    Self.Data[3, 2];
  Result.Y := -Self.Data[1, 0] * Self.Data[3, 0] - Self.Data[1, 1] * Self.Data[3, 1] - Self.Data[1, 2] *
    Self.Data[3, 2];
  Result.Z := -Self.Data[2, 0] * Self.Data[3, 0] - Self.Data[2, 1] * Self.Data[3, 1] - Self.Data[2, 2] *
    Self.Data[3, 2];
end;

function TMatrix4f.WorldPos: TVector3f;
begin
  Result.X := Self.Data[3, 0];
  Result.Y := Self.Data[3, 1];
  Result.Z := Self.Data[3, 2];
end;

function TMatrix4f.Transpose: TMatrix4f;
var
  I, J: Integer;
begin
  for I := 0 to 3 do
    for J := 0 to 3 do
      Result.Data[I, J] := Data[J, I];
end;

function TMatrix4f.Adjoint: TMatrix4f;
begin
  Result.Data[0, 0] := DetSub3(Data[1, 1], Data[2, 1], Data[3, 1], Data[1, 2], Data[2, 2], Data[3, 2],
    Data[1, 3], Data[2, 3], Data[3, 3]);
  Result.Data[1, 0] := -DetSub3(Data[1, 0], Data[2, 0], Data[3, 0], Data[1, 2], Data[2, 2], Data[3, 2],
    Data[1, 3], Data[2, 3], Data[3, 3]);
  Result.Data[2, 0] := DetSub3(Data[1, 0], Data[2, 0], Data[3, 0], Data[1, 1], Data[2, 1], Data[3, 1],
    Data[1, 3], Data[2, 3], Data[3, 3]);
  Result.Data[3, 0] := -DetSub3(Data[1, 0], Data[2, 0], Data[3, 0], Data[1, 1], Data[2, 1], Data[3, 1],
    Data[1, 2], Data[2, 2], Data[3, 2]);

  Result.Data[0, 1] := -DetSub3(Data[0, 1], Data[2, 1], Data[3, 1], Data[0, 2], Data[2, 2], Data[3, 2],
    Data[0, 3], Data[2, 3], Data[3, 3]);
  Result.Data[1, 1] := DetSub3(Data[0, 0], Data[2, 0], Data[3, 0], Data[0, 2], Data[2, 2], Data[3, 2],
    Data[0, 3], Data[2, 3], Data[3, 3]);
  Result.Data[2, 1] := -DetSub3(Data[0, 0], Data[2, 0], Data[3, 0], Data[0, 1], Data[2, 1], Data[3, 1],
    Data[0, 3], Data[2, 3], Data[3, 3]);
  Result.Data[3, 1] := DetSub3(Data[0, 0], Data[2, 0], Data[3, 0], Data[0, 1], Data[2, 1], Data[3, 1],
    Data[0, 2], Data[2, 2], Data[3, 2]);

  Result.Data[0, 2] := DetSub3(Data[0, 1], Data[1, 1], Data[3, 1], Data[0, 2], Data[1, 2], Data[3, 2],
    Data[0, 3], Data[1, 3], Data[3, 3]);
  Result.Data[1, 2] := -DetSub3(Data[0, 0], Data[1, 0], Data[3, 0], Data[0, 2], Data[1, 2], Data[3, 2],
    Data[0, 3], Data[1, 3], Data[3, 3]);
  Result.Data[2, 2] := DetSub3(Data[0, 0], Data[1, 0], Data[3, 0], Data[0, 1], Data[1, 1], Data[3, 1],
    Data[0, 3], Data[1, 3], Data[3, 3]);
  Result.Data[3, 2] := -DetSub3(Data[0, 0], Data[1, 0], Data[3, 0], Data[0, 1], Data[1, 1], Data[3, 1],
    Data[0, 2], Data[1, 2], Data[3, 2]);

  Result.Data[0, 3] := -DetSub3(Data[0, 1], Data[1, 1], Data[2, 1], Data[0, 2], Data[1, 2], Data[2, 2],
    Data[0, 3], Data[1, 3], Data[2, 3]);
  Result.Data[1, 3] := DetSub3(Data[0, 0], Data[1, 0], Data[2, 0], Data[0, 2], Data[1, 2], Data[2, 2],
    Data[0, 3], Data[1, 3], Data[2, 3]);
  Result.Data[2, 3] := -DetSub3(Data[0, 0], Data[1, 0], Data[2, 0], Data[0, 1], Data[1, 1], Data[2, 1],
    Data[0, 3], Data[1, 3], Data[2, 3]);
  Result.Data[3, 3] := DetSub3(Data[0, 0], Data[1, 0], Data[2, 0], Data[0, 1], Data[1, 1], Data[2, 1],
    Data[0, 2], Data[1, 2], Data[2, 2]);
end;

function TMatrix4f.Inverse: TMatrix4f;
begin
  Result := Adjoint * (1.0 / Determinant);
end;

function TMatrix4f.Project(const AVector: TVector3f; const ATargetSize: TPoint2f): TPoint2f;
var
  Last: VectorFloat;
begin
  Last := AVector.X * Data[0, 3] + AVector.Y * Data[1, 3] + AVector.Z * Data[2, 3] + Data[3, 3];

  Result.X := AVector.X * Data[0, 0] + AVector.Y * Data[1, 0] + AVector.Z * Data[2, 0] + Data[3, 0];
  Result.Y := AVector.X * Data[0, 1] + AVector.Y * Data[1, 1] + AVector.Z * Data[2, 1] + Data[3, 1];

  Result := Result / Last;

  Result.X := (Result.X * 0.5 + 0.5) * ATargetSize.X;
  Result.Y := (0.5 - Result.Y * 0.5) * ATargetSize.Y;
end;

class function TMatrix4f.Translate(const AOffset: TVector3f): TMatrix4f;
begin
  Result := TMatrix4f.Identity;
  Result.Data[3, 0] := AOffset.X;
  Result.Data[3, 1] := AOffset.Y;
  Result.Data[3, 2] := AOffset.Z;
end;

class function TMatrix4f.Translate(const AX, AY, AZ: VectorFloat): TMatrix4f;
begin
  Result := Translate(Vector3f(AX, AY, AZ));
end;

class function TMatrix4f.RotateX(const AAngle: VectorFloat): TMatrix4f;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle, LSinValue, LCosValue);

  Result := TMatrix4f.Identity;
  Result.Data[1, 1] := LCosValue;
  Result.Data[1, 2] := LSinValue;
  Result.Data[2, 1] := -Result.Data[1, 2];
  Result.Data[2, 2] := Result.Data[1, 1];
end;

class function TMatrix4f.RotateY(const AAngle: VectorFloat): TMatrix4f;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle, LSinValue, LCosValue);

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := LCosValue;
  Result.Data[0, 2] := -LSinValue;
  Result.Data[2, 0] := -Result.Data[0, 2];
  Result.Data[2, 2] := Result.Data[0, 0];
end;

class function TMatrix4f.RotateZ(const AAngle: VectorFloat): TMatrix4f;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle, LSinValue, LCosValue);

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := LCosValue;
  Result.Data[0, 1] := LSinValue;
  Result.Data[1, 0] := -Result.Data[0, 1];
  Result.Data[1, 1] := Result.Data[0, 0];
end;

class function TMatrix4f.Rotate(const AAxis: TVector3f; const AAngle: VectorFloat): TMatrix4f;
var
  LCosValue, LSinValue: VectorFloat;
  LInvCosValue, LXYMul, LXZMul, LYZMul, LXSin, LYSin, LZSin: VectorFloat;
begin
  SinCos(AAngle, LSinValue, LCosValue);
  LInvCosValue := 1.0 - LCosValue;

  LXYMul := AAxis.X * AAxis.Y * LInvCosValue;
  LXZMul := AAxis.X * AAxis.Z * LInvCosValue;
  LYZMul := AAxis.Y * AAxis.Z * LInvCosValue;

  LXSin := AAxis.X * LSinValue;
  LYSin := AAxis.Y * LSinValue;
  LZSin := AAxis.Z * LSinValue;

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := Sqr(AAxis.X) * LInvCosValue + LCosValue;
  Result.Data[0, 1] := LXYMul + LZSin;
  Result.Data[0, 2] := LXZMul - LYSin;
  Result.Data[1, 0] := LXYMul - LZSin;
  Result.Data[1, 1] := Sqr(AAxis.Y) * LInvCosValue + LCosValue;
  Result.Data[1, 2] := LYZMul + LXSin;
  Result.Data[2, 0] := LXZMul + LYSin;
  Result.Data[2, 1] := LYZMul - LXSin;
  Result.Data[2, 2] := Sqr(AAxis.Z) * LInvCosValue + LCosValue;
end;

class function TMatrix4f.Scale(const AScale: TVector3f): TMatrix4f;
begin
  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := AScale.X;
  Result.Data[1, 1] := AScale.Y;
  Result.Data[2, 2] := AScale.Z;
end;

class function TMatrix4f.Scale(const AX, AY, AZ: VectorFloat): TMatrix4f;
begin
  Result := Scale(Vector3f(AX, AY, AZ));
end;

class function TMatrix4f.Scale(const AScale: VectorFloat): TMatrix4f;
begin
  Result := TMatrix4f.Scale(Vector3f(AScale, AScale, AScale));
end;

class function TMatrix4f.HeadingPitchBank(const AHeading, APitch, ABank: VectorFloat): TMatrix4f;
var
  LCosHeading, LSinHeading, LCosPitch: VectorFloat;
  LSinPitch, LCosBank, LSinBank: VectorFloat;
begin
  SinCos(AHeading, LSinHeading, LCosHeading);
  SinCos(APitch, LSinPitch, LCosPitch);
  SinCos(ABank, LSinBank, LCosBank);

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := LCosHeading * LCosBank + LSinHeading * LSinPitch * LSinBank;
  Result.Data[0, 1] := LSinHeading * LSinPitch * LCosBank - LCosHeading * LSinBank;
  Result.Data[0, 2] := LSinHeading * LCosPitch;
  Result.Data[1, 0] := LSinBank * LCosPitch;
  Result.Data[1, 1] := LCosBank * LCosPitch;
  Result.Data[1, 2] := - LSinPitch;
  Result.Data[2, 0] := LCosHeading * LSinPitch * LSinBank - LSinHeading * LCosBank;
  Result.Data[2, 1] := LSinBank * LSinHeading + LCosHeading * LSinPitch * LCosBank;
  Result.Data[2, 2] := LCosHeading * LCosPitch;
end;

class function TMatrix4f.HeadingPitchBank(const AVector: TVector3f): TMatrix4f;
begin
  Result := HeadingPitchBank(AVector.Y, AVector.X, AVector.Z);
end;

class function TMatrix4f.YawPitchRoll(const AYaw, APitch, ARoll: VectorFloat): TMatrix4f;
var
  LSinYaw, LCosYaw, LSinPitch: VectorFloat;
  LCosPitch, LSinRoll, LCosRoll: VectorFloat;
begin
  SinCos(AYaw, LSinYaw, LCosYaw);
  SinCos(APitch, LSinPitch, LCosPitch);
  SinCos(ARoll, LSinRoll, LCosRoll);

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := LCosRoll * LCosYaw + LSinPitch * LSinRoll * LSinYaw;
  Result.Data[0, 1] := LCosYaw * LSinPitch * LSinRoll - LCosRoll * LSinYaw;
  Result.Data[0, 2] := -LCosPitch * LSinRoll;
  Result.Data[1, 0] := LCosPitch * LSinYaw;
  Result.Data[1, 1] := LCosPitch * LCosYaw;
  Result.Data[1, 2] := LSinPitch;
  Result.Data[2, 0] := LCosYaw * LSinRoll - LCosRoll * LSinPitch * LSinYaw;
  Result.Data[2, 1] := -LCosRoll * LCosYaw * LSinPitch - LSinRoll * LSinYaw;
  Result.Data[2, 2] := LCosPitch * LCosRoll;
end;

class function TMatrix4f.YawPitchRoll(const AVector: TVector3f): TMatrix4f;
begin
  Result := YawPitchRoll(AVector.Y, AVector.X, AVector.Z);
end;

class function TMatrix4f.Reflect(const AAxis: TVector3f): TMatrix4f;
var
  LXYMul, LYZMul, LXZMul: VectorFloat;
begin
  LXYMul := -2.0 * AAxis.X * AAxis.Y;
  LXZMul := -2.0 * AAxis.X * AAxis.Z;
  LYZMul := -2.0 * AAxis.Y * AAxis.Z;

  Result := TMatrix4f.Identity;
  Result.Data[0, 0] := 1.0 - (2.0 * Sqr(AAxis.X));
  Result.Data[0, 1] := LXYMul;
  Result.Data[0, 2] := LXZMul;
  Result.Data[1, 0] := LXYMul;
  Result.Data[1, 1] := 1.0 - (2.0 * Sqr(AAxis.Y));
  Result.Data[1, 2] := LYZMul;
  Result.Data[2, 0] := LXZMul;
  Result.Data[2, 1] := LYZMul;
  Result.Data[2, 2] := 1.0 - (2.0 * Sqr(AAxis.Z));
end;

class function TMatrix4f.LookAt(const AOrigin, ATarget, ACeiling: TVector3f): TMatrix4f;
var
  LXAxis, LYAxis, LZAxis: TVector3f;
begin
  LZAxis := (ATarget - AOrigin).Normalize;
  LXAxis := ACeiling.Cross(LZAxis).Normalize;
  LYAxis := LZAxis.Cross(LXAxis);

  Result.Data[0, 0] := LXAxis.X;
  Result.Data[0, 1] := LYAxis.X;
  Result.Data[0, 2] := LZAxis.X;
  Result.Data[0, 3] := 0.0;

  Result.Data[1, 0] := LXAxis.Y;
  Result.Data[1, 1] := LYAxis.Y;
  Result.Data[1, 2] := LZAxis.Y;
  Result.Data[1, 3] := 0.0;

  Result.Data[2, 0] := LXAxis.Z;
  Result.Data[2, 1] := LYAxis.Z;
  Result.Data[2, 2] := LZAxis.Z;
  Result.Data[2, 3] := 0.0;

  Result.Data[3, 0] := -LXAxis.Dot(AOrigin);
  Result.Data[3, 1] := -LYAxis.Dot(AOrigin);
  Result.Data[3, 2] := -LZAxis.Dot(AOrigin);
  Result.Data[3, 3] := 1.0;
end;

class function TMatrix4f.PerspectiveFOVY(const AFieldOfView, AAspectRatio, AMinRange,
  AMaxRange: VectorFloat; const ADepthClipNegative: Boolean): TMatrix4f;
var
  LXScale, LYScale: VectorFloat;
begin
  Result := TMatrix4f.Zero;

  LYScale := Cot(AFieldOfView * 0.5);
  LXScale := LYScale / AAspectRatio;

  Result.Data[0, 0] := LXScale;
  Result.Data[1, 1] := LYScale;
  Result.Data[2, 3] := 1.0;

  if ADepthClipNegative then
  begin
    Result.Data[2, 2] := (AMinRange + AMaxRange) / (AMaxRange - AMinRange);
    Result.Data[3, 2] := -2.0 * AMinRange * AMaxRange / (AMaxRange - AMinRange);
  end
  else
  begin
    Result.Data[2, 2] := AMinRange / (AMinRange - AMaxRange);
    Result.Data[3, 2] := -AMaxRange * Result.Data[2, 2];
  end;
end;

class function TMatrix4f.PerspectiveFOVX(const AFieldOfView, AAspectRatio, AMinRange,
  AMaxRange: VectorFloat; const ADepthClipNegative: Boolean): TMatrix4f;
var
  LXScale, LYScale: VectorFloat;
begin
  Result := TMatrix4f.Zero;

  LXScale := Cot(AFieldOfView * 0.5);
  LYScale := LXScale / AAspectRatio;

  Result.Data[0, 0] := LXScale;
  Result.Data[1, 1] := LYScale;
  Result.Data[2, 3] := 1.0;

  if ADepthClipNegative then
  begin
    Result.Data[2, 2] := (AMinRange + AMaxRange) / (AMaxRange - AMinRange);
    Result.Data[3, 2] := -2.0 * AMinRange * AMaxRange / (AMaxRange - AMinRange);
  end
  else
  begin
    Result.Data[2, 2] := AMinRange / (AMinRange - AMaxRange);
    Result.Data[3, 2] := -AMaxRange * Result.Data[2, 2];
  end;
end;

class function TMatrix4f.PerspectiveVOL(const AWidth, AHeight, AMinRange, AMaxRange: VectorFloat): TMatrix4f;
begin
  Result := TMatrix4f.Zero;

  Result.Data[0, 0] := (2.0 * AMinRange) / AWidth;
  Result.Data[1, 1] := (2.0 * AMinRange) / AHeight;
  Result.Data[2, 2] := AMaxRange / (AMaxRange - AMinRange);
  Result.Data[2, 3] := 1.0;
  Result.Data[3, 2] := AMinRange * (AMinRange - AMaxRange);
end;

class function TMatrix4f.PerspectiveBDS(const ALeft, ARight, ATop, ABottom, AMinRange,
  AMaxRange: VectorFloat): TMatrix4f;
begin
  Result := TMatrix4f.Zero;

  Result.Data[0, 0] := (2.0 * AMinRange) / (ARight - ALeft);
  Result.Data[1, 1] := (2.0 * AMinRange) / (ATop - ABottom);

  Result.Data[2, 0] := (ALeft + ARight) / (ALeft - ARight);
  Result.Data[2, 1] := (ATop + ABottom) / (ABottom - ATop);
  Result.Data[2, 2] := AMaxRange / (AMaxRange - AMinRange);
  Result.Data[2, 3] := 1.0;
  Result.Data[3, 2] := AMinRange * AMaxRange / (AMinRange - AMaxRange);
end;

class function TMatrix4f.OrthogonalVOL(const AWidth, AHeight, AMinRange, AMaxRange: VectorFloat): TMatrix4f;
begin
  Result := TMatrix4f.Zero;

  Result.Data[0, 0] := 2.0 / AWidth;
  Result.Data[1, 1] := 2.0 / AHeight;
  Result.Data[2, 2] := 1.0 / (AMinRange - AMaxRange);
  Result.Data[3, 2] := -AMaxRange / (AMinRange - AMaxRange);
  Result.Data[3, 3] := 1.0;
end;

class function TMatrix4f.OrthogonalBDS(const ALeft, ARight, ATop, ABottom, AMinRange,
  AMaxRange: VectorFloat): TMatrix4f;
begin
  Result := TMatrix4f.Zero;

  Result.Data[0, 0] := 2.0 / (ARight - ALeft);
  Result.Data[1, 1] := 2.0 / (ABottom - ATop);
  Result.Data[2, 2] := 1.0 / (AMinRange - AMaxRange);
  Result.Data[3, 0] := (ALeft + ARight) / (ALeft - ARight);
  Result.Data[3, 1] := (ATop + ABottom) / (ABottom - ATop);
  Result.Data[3, 2] := -AMaxRange / (AMinRange - AMaxRange);
  Result.Data[3, 3] := 1.0;
end;

{$ENDREGION}
{$REGION 'TQuaternion functions'}

class operator TQuaternion.Multiply(const AQuaternion1, AQuaternion2: TQuaternion): TQuaternion;
begin
  Result.X := AQuaternion2.W * AQuaternion1.X + AQuaternion2.X * AQuaternion1.W + AQuaternion2.Z * AQuaternion1.Y -
    AQuaternion2.Y * AQuaternion1.Z;
  Result.Y := AQuaternion2.W * AQuaternion1.Y + AQuaternion2.Y * AQuaternion1.W + AQuaternion2.X * AQuaternion1.Z -
    AQuaternion2.Z * AQuaternion1.X;
  Result.Z := AQuaternion2.W * AQuaternion1.Z + AQuaternion2.Z * AQuaternion1.W + AQuaternion2.Y * AQuaternion1.X -
    AQuaternion2.X * AQuaternion1.Y;
  Result.W := AQuaternion2.W * AQuaternion1.W - AQuaternion2.X * AQuaternion1.X - AQuaternion2.Y * AQuaternion1.Y -
    AQuaternion2.Z * AQuaternion1.Z;
end;

class operator TQuaternion.Implicit(const AQuaternion: TQuaternion): TMatrix4f;
begin
  Result.Data[0, 0] := 1.0 - 2.0 * AQuaternion.Y * AQuaternion.Y - 2.0 * AQuaternion.Z * AQuaternion.Z;
  Result.Data[0, 1] := 2.0 * AQuaternion.X * AQuaternion.Y + 2.0 * AQuaternion.W * AQuaternion.Z;
  Result.Data[0, 2] := 2.0 * AQuaternion.X * AQuaternion.Z - 2.0 * AQuaternion.W * AQuaternion.Y;
  Result.Data[0, 3] := 0.0;
  Result.Data[1, 0] := 2.0 * AQuaternion.X * AQuaternion.Y - 2.0 * AQuaternion.W * AQuaternion.Z;
  Result.Data[1, 1] := 1.0 - 2.0 * AQuaternion.X * AQuaternion.X - 2 * AQuaternion.Z * AQuaternion.Z;
  Result.Data[1, 2] := 2.0 * AQuaternion.Y * AQuaternion.Z + 2.0 * AQuaternion.W * AQuaternion.X;
  Result.Data[1, 3] := 0.0;
  Result.Data[2, 0] := 2.0 * AQuaternion.X * AQuaternion.Z + 2.0 * AQuaternion.W * AQuaternion.Y;
  Result.Data[2, 1] := 2.0 * AQuaternion.Y * AQuaternion.Z - 2.0 * AQuaternion.W * AQuaternion.X;
  Result.Data[2, 2] := 1.0 - 2.0 * AQuaternion.X * AQuaternion.X - 2.0 * AQuaternion.Y * AQuaternion.Y;
  Result.Data[2, 3] := 0.0;
  Result.Data[3, 0] := 0.0;
  Result.Data[3, 1] := 0.0;
  Result.Data[3, 2] := 0.0;
  Result.Data[3, 3] := 1.0;
end;

class operator TQuaternion.Explicit(const AMatrix: TMatrix4f): TQuaternion;
var
  LMaxValue, LHighValue, LMultValue: VectorFloat;
  LTempQuat: TQuaternion;
  LIndex: Integer;
begin
  // Determine wich of W, X, Y, Z has the largest absolute value.
  LTempQuat.X := AMatrix.Data[0, 0] - AMatrix.Data[1, 1] - AMatrix.Data[2, 2];
  LTempQuat.Y := AMatrix.Data[1, 1] - AMatrix.Data[0, 0] - AMatrix.Data[2, 2];
  LTempQuat.Z := AMatrix.Data[2, 2] - AMatrix.Data[0, 0] - AMatrix.Data[1, 1];
  LTempQuat.W := AMatrix.Data[0, 0] + AMatrix.Data[1, 1] + AMatrix.Data[2, 2];

  LIndex := 0;
  LMaxValue := LTempQuat.W;
  if LTempQuat.X > LMaxValue then
  begin
    LMaxValue := LTempQuat.X;
    LIndex := 1;
  end;
  if LTempQuat.Y > LMaxValue then
  begin
    LMaxValue := LTempQuat.Y;
    LIndex := 2;
  end;
  if LTempQuat.Z > LMaxValue then
  begin
    LMaxValue := LTempQuat.Z;
    LIndex := 3;
  end;

  // Perform square root and division.
  LHighValue := Sqrt(LMaxValue + 1.0) * 0.5;
  LMultValue := 0.25 / LHighValue;

  // Apply table to compute Aquaternion values.
  case LIndex of
    0:
      begin
        Result.W := LHighValue;
        Result.X := (AMatrix.Data[1, 2] - AMatrix.Data[2, 1]) * LMultValue;
        Result.Y := (AMatrix.Data[2, 0] - AMatrix.Data[0, 2]) * LMultValue;
        Result.Z := (AMatrix.Data[0, 1] - AMatrix.Data[1, 0]) * LMultValue;
      end;
    1:
      begin
        Result.X := LHighValue;
        Result.W := (AMatrix.Data[1, 2] - AMatrix.Data[2, 1]) * LMultValue;
        Result.Z := (AMatrix.Data[2, 0] + AMatrix.Data[0, 2]) * LMultValue;
        Result.Y := (AMatrix.Data[0, 1] + AMatrix.Data[1, 0]) * LMultValue;
      end;
    2:
      begin
        Result.Y := LHighValue;
        Result.Z := (AMatrix.Data[1, 2] + AMatrix.Data[2, 1]) * LMultValue;
        Result.W := (AMatrix.Data[2, 0] - AMatrix.Data[0, 2]) * LMultValue;
        Result.X := (AMatrix.Data[0, 1] + AMatrix.Data[1, 0]) * LMultValue;
      end;
  else
    begin
      Result.Z := LHighValue;
      Result.Y := (AMatrix.Data[1, 2] + AMatrix.Data[2, 1]) * LMultValue;
      Result.X := (AMatrix.Data[2, 0] + AMatrix.Data[0, 2]) * LMultValue;
      Result.W := (AMatrix.Data[0, 1] - AMatrix.Data[1, 0]) * LMultValue;
    end;
  end;
end;

class operator TQuaternion.Equal(const AQuaternion1, AQuaternion2: TQuaternion): Boolean;
begin
  Result := NearlyEqual(AQuaternion1.X, AQuaternion2.X) and NearlyEqual(AQuaternion1.Y, AQuaternion2.Y) and
    NearlyEqual(AQuaternion1.Z, AQuaternion2.Z) and NearlyEqual(AQuaternion1.W, AQuaternion2.W);
end;

class operator TQuaternion.NotEqual(const AQuaternion1, AQuaternion2: TQuaternion): Boolean;
begin
  Result := not (AQuaternion1 = AQuaternion2);
end;

function TQuaternion.Length: VectorFloat;
begin
  Result := Sqrt((X * X) + (Y * Y) + (Z * Z) + (W * W));
end;

function TQuaternion.Normalize: TQuaternion;
var
  LAmp, LInvMag: VectorFloat;
begin
  LAmp := Length;
  if LAmp > VectorEpsilon then
  begin
    LInvMag := 1.0 / LAmp;
    Result.X := X * LInvMag;
    Result.Y := Y * LInvMag;
    Result.Z := Z * LInvMag;
    Result.W := W * LInvMag;
  end
  else
    Result := Self;
end;

function TQuaternion.Angle: VectorFloat;
begin
  Result := ArcCos(W) * 2.0;
end;

function TQuaternion.Axis: TVector3f;
var
  LTemp1, LTemp2: VectorFloat;
begin
  LTemp1 := 1.0 - (W * W);
  if LTemp1 <= 0.0 then
  begin
    Result := TVector3f.AxisY;
    Exit;
  end;

  LTemp2 := 1.0 / Sqrt(LTemp1);

  Result.X := X * LTemp2;
  Result.Y := Y * LTemp2;
  Result.Z := Z * LTemp2;
end;

function TQuaternion.Conjugate: TQuaternion;
begin
  Result.X := -X;
  Result.Y := -Y;
  Result.Z := -Z;
  Result.W := W;
end;

function TQuaternion.Exponentiate(const AExponent: VectorFloat): TQuaternion;
var
  LCosNewAlpha, LSinNewAlpha: VectorFloat;
  LAlpha, LNewAlpha, LCompMult: VectorFloat;
begin
  if Abs(W) > 1.0 - VectorEpsilon then
  begin
    Result := Self;
    Exit;
  end;

  LAlpha := ArcCos(W);
  LNewAlpha := LAlpha * AExponent;

  SinCos(LNewAlpha, LSinNewAlpha, LCosNewAlpha);

  LCompMult := LSinNewAlpha / Sin(LAlpha);

  Result.X := X * LCompMult;
  Result.Y := Y * LCompMult;
  Result.Z := Z * LCompMult;
  Result.W := LCosNewAlpha;
end;

function TQuaternion.Dot(const AQuaternion: TQuaternion): VectorFloat;
begin
  Result := (X * AQuaternion.X) + (Y * AQuaternion.Y) + (Z * AQuaternion.Z) + (W * AQuaternion.W);
end;

function TQuaternion.Slerp(const AQuaternion: TQuaternion; const ATheta: VectorFloat): TQuaternion;
var
  LTempQuat: TQuaternion;
  SinOmega, CosOmega, Omega, Kappa1, Kappa2: VectorFloat;
  OneOverSinOmega: VectorFloat;
begin
  if ATheta <= 0.0 then
  begin
    Result := Self;
    Exit;
  end
  else if ATheta >= 1.0 then
  begin
    Result := AQuaternion;
    Exit;
  end;

  CosOmega := Dot(AQuaternion);
  LTempQuat := AQuaternion;

  if CosOmega < 0.0 then
  begin
    LTempQuat.X := -LTempQuat.X;
    LTempQuat.Y := -LTempQuat.Y;
    LTempQuat.Z := -LTempQuat.Z;
    LTempQuat.W := -LTempQuat.W;
    CosOmega := -CosOmega;
  end;

  if CosOmega > 1.0 - VectorEpsilon then
  begin
    Kappa1 := 1.0 - ATheta;
    Kappa2 := ATheta;
  end
  else
  begin
    SinOmega := Sqrt(1.0 - CosOmega * CosOmega);
    Omega := ArcTan2(SinOmega, CosOmega);

    OneOverSinOmega := 1.0 / SinOmega;

    Kappa1 := Sin((1.0 - ATheta) * Omega) * OneOverSinOmega;
    Kappa2 := Sin(ATheta * Omega) * OneOverSinOmega;
  end;

  Result.X := Kappa1 * X + Kappa2 * LTempQuat.X;
  Result.Y := Kappa1 * Y + Kappa2 * LTempQuat.Y;
  Result.Z := Kappa1 * Z + Kappa2 * LTempQuat.Z;
  Result.W := Kappa1 * W + Kappa2 * LTempQuat.W;
end;

function TQuaternion.Angles: TVector3f;
var
  LX, LY, LZ, LW: Double;
begin
  LX := X;
  LY := Y;
  LZ := Z;
  LW := W;

  Result.X := ArcTan(2.0 * (LW * LX + LY * LZ) / Sqrt(Sqr(2.0 * (LX * LY - LW * LZ)) +
    Sqr(1.0 - 2.0 * (Sqr(LX) + Sqr(LZ)))));

  if (Result.X >= Pi * 0.5 - VectorEpsilon) then
  begin
    Result.Z := 0.0;
    Result.Y := -ArcTan2(2.0 * (LW * LY + LX * LZ), 2.0 * (LY * LZ - LW * LX));
  end
  else if (Result.X <= -Pi * 0.5 + VectorEpsilon) then
  begin
    Result.Z := 0.0;
    Result.Y := ArcTan2(2.0 * (LW * LY + LX * LZ) , 1.0 - 2.0 * (Sqr(LY) + Sqr(LZ)));
  end
  else
  begin
    Result.Z := ArcTan2(2.0 * (LX * LY - LW * LZ), 1.0 - 2.0 * (Sqr(LX) + Sqr(LZ)));
    Result.Y := ArcTan2(-2.0 * (LX * LZ - LW * LY), 1.0 - 2.0 * (Sqr(LX) + Sqr(LY)));
  end;

  if (Result.Y < 0.0) then
    Result.Y := Result.Y + Pi * 2.0;
end;

class function TQuaternion.RotateX(const AAngle: VectorFloat): TQuaternion;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle * 0.5, LSinValue, LCosValue);

  Result.X := LSinValue;
  Result.Y := 0.0;
  Result.Z := 0.0;
  Result.W := LCosValue;
end;

class function TQuaternion.RotateY(const AAngle: VectorFloat): TQuaternion;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle * 0.5, LSinValue, LCosValue);

  Result.X := 0.0;
  Result.Y := LSinValue;
  Result.Z := 0.0;
  Result.W := LCosValue;
end;

class function TQuaternion.RotateZ(const AAngle: VectorFloat): TQuaternion;
var
  LSinValue, LCosValue: VectorFloat;
begin
  SinCos(AAngle * 0.5, LSinValue, LCosValue);

  Result.X := 0.0;
  Result.Y := 0.0;
  Result.Z := -LSinValue;
  Result.W := LCosValue;
end;

class function TQuaternion.Rotate(const AAxis: TVector3f; const AAngle: VectorFloat): TQuaternion;
var
  LSinValue, LCosValue: VectorFloat;
  LAmp, LSinDivAmp: VectorFloat;
begin
  LAmp := AAxis.Length;
  if LAmp > VectorEpsilon then
  begin
    SinCos(AAngle * 0.5, LSinValue, LCosValue);
    LSinDivAmp := LSinValue / LAmp;

    Result.X := AAxis.X * LSinDivAmp;
    Result.Y := AAxis.Y * LSinDivAmp;
    Result.Z := -AAxis.Z * LSinDivAmp;
    Result.W := LCosValue;
  end
  else
    Result := TQuaternion.Identity;
end;

class function TQuaternion.YawPitchRoll(const AYaw, APitch, ARoll: VectorFloat): TQuaternion;
var
  LSinYaw, LCosYaw, LSinPitch, LCosPitch, LSinRoll, LCosRoll: VectorFloat;
begin
  SinCos(AYaw * 0.5, LSinYaw, LCosYaw);
  SinCos(APitch * 0.5, LSinPitch, LCosPitch);
  SinCos(ARoll * 0.5, LSinRoll, LCosRoll);

  Result.X := LCosYaw * LSinPitch * LCosRoll + LSinYaw * LCosPitch * LSinRoll;
  Result.Y := -LCosYaw * LSinPitch * LSinRoll + LSinYaw * LCosPitch * LCosRoll;
  Result.Z := -LCosYaw * LCosPitch * LSinRoll + LSinYaw * LSinPitch * LCosRoll;
  Result.W := LCosYaw * LCosPitch * LCosRoll + LSinYaw * LSinPitch * LSinRoll;
end;

class function TQuaternion.YawPitchRoll(const AAngles: TVector3f): TQuaternion;
begin
  Result := YawPitchRoll(AAngles.Y, AAngles.X, AAngles.Z);
end;

class function TQuaternion.RotateObjectToIntertial(const AHeading, APitch, ABank: VectorFloat): TQuaternion;
var
  LSinPitch, LSinBank, LSinHeading: VectorFloat;
  LCosPitch, LCosBank, LCosHeading: VectorFloat;
begin
  SinCos(AHeading * 0.5, LSinHeading, LCosHeading);
  SinCos(APitch * 0.5, LSinPitch, LCosPitch);
  SinCos(ABank * 0.5, LSinBank, LCosBank);

  Result.X := LCosHeading * LSinPitch * LCosBank + LSinHeading * LCosPitch * LSinBank;
  Result.Y := -LCosHeading * LSinPitch * LSinBank + LSinHeading * LCosPitch * LCosBank;
  Result.Z := -LSinHeading * LSinPitch * LCosBank + LCosHeading * LCosPitch * LSinBank;
  Result.W := LCosHeading * LCosPitch * LCosBank + LSinHeading * LSinPitch * LSinBank;
end;

class function TQuaternion.RotateInertialToObject(const AHeading, APitch, ABank: VectorFloat): TQuaternion;
var
  LSinPitch, LSinBank, LSinHeading: VectorFloat;
  LCosPitch, LCosBank, LCosHeading: VectorFloat;
begin
  SinCos(AHeading * 0.5, LSinHeading, LCosHeading);
  SinCos(APitch * 0.5, LSinPitch, LCosPitch);
  SinCos(ABank * 0.5, LSinBank, LCosBank);

  Result.X := -LCosHeading * LSinPitch * LCosBank - LSinHeading * LCosPitch * LSinBank;
  Result.Y := LCosHeading * LSinPitch * LSinBank - LSinHeading * LCosPitch * LCosBank;
  Result.Z := LSinHeading * LSinPitch * LCosBank - LCosHeading * LCosPitch * LSinBank;
  Result.W := LCosHeading * LCosPitch * LCosBank + LSinHeading * LSinPitch * LSinBank;
end;

function Quaternion(const AX, AY, AZ, AW: VectorFloat): TQuaternion;
begin
  Result.X := AX;
  Result.Y := AY;
  Result.Z := AZ;
  Result.W := AW;
end;

{$ENDREGION}
{$REGION 'TIntRect'}

class operator TIntRect.Equal(const ARect1, ARect2: TIntRect): Boolean;
begin
  Result := (ARect1.TopLeft = ARect2.TopLeft) and (ARect1.Size = ARect2.Size);
end;

class operator TIntRect.NotEqual(const ARect1, ARect2: TIntRect): Boolean;
begin
  Result := not (ARect1 = ARect2);
end;

function TIntRect.GetRight: VectorInt;
begin
  Result := Left + Width;
end;

procedure TIntRect.SetRight(const Value: VectorInt);
begin
  Width := Value - Left;
end;

function TIntRect.GetBottom: VectorInt;
begin
  Result := Top + Height;
end;

procedure TIntRect.SetBottom(const Value: VectorInt);
begin
  Height := Value - Top;
end;

function TIntRect.GetBottomRight: TPoint2i;
begin
  Result.X := GetRight;
  Result.Y := GetBottom;
end;

procedure TIntRect.SetBottomRight(const Value: TPoint2i);
begin
  SetRight(Value.X);
  SetBottom(Value.Y);
end;

function TIntRect.Empty: Boolean;
begin
  Result := (Width <= 0) or (Height <= 0);
end;

function TIntRect.Contains(const APoint: TPoint2i): Boolean;
begin
  Result := (APoint.X >= Left) and (APoint.X < Right) and (APoint.Y >= Top) and (APoint.Y < Bottom);
end;

function TIntRect.Contains(const ARect: TIntRect): Boolean;
begin
  Result := (ARect.Left >= Left) and (ARect.Right <= Right) and (ARect.Top >= Top) and (ARect.Bottom <= Bottom);
end;

function TIntRect.Overlaps(const ARect: TIntRect): Boolean;
begin
  Result := (ARect.Left < Right) and (ARect.Right > Left) and (ARect.Top < Bottom) and (ARect.Bottom > Top);
end;

function TIntRect.Intersect(const ARect: TIntRect): TIntRect;
begin
  if Overlaps(ARect) then
    Result := IntRectBDS(
      Max(Left, ARect.Left), Max(Top, ARect.Top),
      Min(Right, ARect.Right), Min(Bottom, ARect.Bottom))
  else
    Result := TIntRect.Zero;
end;

function TIntRect.Union(const ARect: TIntRect): TIntRect;
begin
  Result := IntRectBDS(
    Min(Left, ARect.Left), Min(Top, ARect.Top),
    Max(Right, ARect.Right), Max(Bottom, ARect.Bottom));
end;

function TIntRect.Offset(const ADelta: TPoint2i): TIntRect;
begin
  Result := IntRectBDS(TopLeft + ADelta, BottomRight + ADelta);
end;

function TIntRect.Offset(const ADeltaX, ADeltaY: VectorInt): TIntRect;
begin
  Result := Offset(Point2i(ADeltaX, ADeltaY));
end;

function TIntRect.Inflate(const ADelta: TPoint2i): TIntRect;
begin
  Result := IntRectBDS(TopLeft - ADelta, BottomRight + ADelta);
end;

function TIntRect.Inflate(const ADeltaX, ADeltaY: VectorInt): TIntRect;
begin
  Result := Inflate(Point2i(ADeltaX, ADeltaY));
end;

function TIntRect.Inflate(const ADelta: VectorInt): TIntRect;
begin
  Result := Inflate(Point2i(ADelta, ADelta));
end;

class function TIntRect.ClipCoords(const ASourceSize, ADestSize: TPoint2i; var ASourceRect: TIntRect;
  var ADestPos: TPoint2i): Boolean;
var
  LDelta: VectorInt;
begin
  if ASourceRect.Left < 0 then
  begin
    LDelta := -ASourceRect.Left;
    Inc(ASourceRect.Left, LDelta);
    Inc(ADestPos.X, LDelta);
  end;

  if ASourceRect.Top < 0 then
  begin
    LDelta := -ASourceRect.Top;
    Inc(ASourceRect.Top, LDelta);
    Inc(ADestPos.Y, LDelta);
  end;

  if ASourceRect.Right > ASourceSize.X then
    ASourceRect.Right := ASourceSize.X;

  if ASourceRect.Bottom > ASourceSize.Y then
    ASourceRect.Bottom := ASourceSize.Y;

  if ADestPos.X < 0 then
  begin
    LDelta := -ADestPos.X;
    Inc(ADestPos.X, LDelta);
    Inc(ASourceRect.Left, LDelta);
  end;

  if ADestPos.Y < 0 then
  begin
    LDelta := -ADestPos.Y;
    Inc(ADestPos.Y, LDelta);
    Inc(ASourceRect.Top, LDelta);
  end;

  if ADestPos.X + ASourceRect.Width > ADestSize.X then
  begin
    LDelta := ADestPos.X + ASourceRect.Width - ADestSize.X;
    Dec(ASourceRect.Width, LDelta);
  end;

  if ADestPos.Y + ASourceRect.Height > ADestSize.Y then
  begin
    LDelta := ADestPos.Y + ASourceRect.Height - ADestSize.Y;
    Dec(ASourceRect.Height, LDelta);
  end;

  Result := not ASourceRect.Empty;
end;

function IntRect(const ALeft, ATop, AWidth, AHeight: VectorInt): TIntRect;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Width := AWidth;
  Result.Height := AHeight;
end;

function IntRect(const AOrigin, ASize: TPoint2i): TIntRect;
begin
  Result.TopLeft := AOrigin;
  Result.Size := ASize;
end;

function IntRectBDS(const ALeft, ATop, ARight, ABottom: VectorInt): TIntRect;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Right := ARight;
  Result.Bottom := ABottom;
end;

function IntRectBDS(const ATopLeft, ABottomRight: TPoint2i): TIntRect;
begin
  Result.TopLeft := ATopLeft;
  Result.BottomRight := ABottomRight;
end;

{$ENDREGION}
{$REGION 'TFloatRect'}

class operator TFloatRect.Implicit(const ARect: TIntRect): TFloatRect;
begin
  Result.Left := ARect.Left;
  Result.Top := ARect.Top;
  Result.Width := ARect.Width;
  Result.Height := ARect.Height;
end;

class operator TFloatRect.Equal(const ARect1, ARect2: TFloatRect): Boolean;
begin
  Result := (ARect1.TopLeft = ARect2.TopLeft) and (ARect1.BottomRight = ARect2.BottomRight);
end;

class operator TFloatRect.NotEqual(const ARect1, ARect2: TFloatRect): Boolean;
begin
  Result := not (ARect1 = ARect2);
end;

class operator TFloatRect.Multiply(const ARect: TFloatRect; const AScale: VectorFloat): TFloatRect;
begin
  Result.Left := ARect.Left * AScale;
  Result.Top := ARect.Top * AScale;
  Result.Width := ARect.Width * AScale;
  Result.Height := ARect.Height * AScale;
end;

function TFloatRect.GetRight: VectorFloat;
begin
  Result := Left + Width;
end;

procedure TFloatRect.SetRight(const Value: VectorFloat);
begin
  Width := Value - Left;
end;

function TFloatRect.GetBottom: VectorFloat;
begin
  Result := Top + Height;
end;

procedure TFloatRect.SetBottom(const Value: VectorFloat);
begin
  Height := Value - Top;
end;

function TFloatRect.GetBottomRight: TPoint2f;
begin
  Result.X := GetRight;
  Result.Y := GetBottom;
end;

procedure TFloatRect.SetBottomRight(const Value: TPoint2f);
begin
  SetRight(Value.X);
  SetBottom(Value.Y);
end;

function TFloatRect.Empty: Boolean;
begin
  Result := (Width < VectorEpsilon) or (Height < VectorEpsilon);
end;

function TFloatRect.Contains(const APoint: TPoint2f): Boolean;
begin
  Result := (APoint.X >= Left) and (APoint.X < Right) and (APoint.Y >= Top) and (APoint.Y < Bottom);
end;

function TFloatRect.Contains(const ARect: TFloatRect): Boolean;
begin
  Result := (ARect.Left >= Left) and (ARect.Right <= Right) and (ARect.Top >= Top) and (ARect.Bottom <= Bottom);
end;

function TFloatRect.Overlaps(const ARect: TFloatRect): Boolean;
begin
  Result := (ARect.Left < Right) and (ARect.Right > Left) and (ARect.Top < Bottom) and (ARect.Bottom > Top);
end;

function TFloatRect.Intersect(const ARect: TFloatRect): TFloatRect;
begin
  if Overlaps(ARect) then
    Result := FloatRectBDS(
      Max(Left, ARect.Left), Max(Top, ARect.Top),
      Min(Right, ARect.Right), Min(Bottom, ARect.Bottom))
  else
    Result := TFloatRect.Zero;
end;

function TFloatRect.Union(const ARect: TFloatRect): TFloatRect;
begin
  Result := FloatRectBDS(
    Min(Left, ARect.Left), Min(Top, ARect.Top),
    Max(Right, ARect.Right), Max(Bottom, ARect.Bottom));
end;

function TFloatRect.Offset(const ADelta: TPoint2f): TFloatRect;
begin
  Result := FloatRectBDS(TopLeft + ADelta, BottomRight + ADelta);
end;

function TFloatRect.Offset(const ADeltaX, ADeltaY: VectorFloat): TFloatRect;
begin
  Result := Offset(Point2f(ADeltaX, ADeltaY));
end;

function TFloatRect.Inflate(const ADelta: TPoint2f): TFloatRect;
begin
  Result := FloatRectBDS(TopLeft - ADelta, BottomRight + ADelta);
end;

function TFloatRect.Inflate(const ADeltaX, ADeltaY: VectorFloat): TFloatRect;
begin
  Result := Inflate(Point2f(ADeltaX, ADeltaY));
end;

function TFloatRect.Inflate(const ADelta: VectorFloat): TFloatRect;
begin
  Result := Inflate(Point2f(ADelta, ADelta));
end;

function TFloatRect.InnerBounds: TIntRect;
begin
  Result.TopLeft := Point2i(Floor(Left), Floor(Top));
  Result.Width := Ceil(Right) - Result.Left;
  Result.Height := Ceil(Bottom) - Result.Top;
end;

function TFloatRect.OuterBounds: TIntRect;
begin
  Result.TopLeft := Point2i(Ceil(Left), Ceil(Top));
  Result.Width := Floor(Right) - Result.Left;
  Result.Height := Floor(Bottom) - Result.Top;
end;

class function TFloatRect.ClipCoords(const ASourceSize, ADestSize: TPoint2f; var ASourceRect,
  ADestRect: TFloatRect): Boolean;
var
  LDelta: VectorFloat;
  LScale: TPoint2f;
begin
  if ASourceRect.Empty or ADestRect.Empty then
  begin
    Result := False;
    Exit;
  end;

  LScale.X := ADestRect.Width / ASourceRect.Width;
  LScale.Y := ADestRect.Height / ASourceRect.Height;

  if ASourceRect.Left < 0 then
  begin
    LDelta := -ASourceRect.Left;
    ASourceRect.Left := ASourceRect.Left + LDelta;
    ADestRect.Left := ADestRect.Left + (LDelta * LScale.X);
  end;

  if ASourceRect.Top < 0 then
  begin
    LDelta := -ASourceRect.Top;
    ASourceRect.Top := ASourceRect.Top + LDelta;
    ADestRect.Top := ADestRect.Top + (LDelta * LScale.Y);
  end;

  if ASourceRect.Right > ASourceSize.X then
  begin
    LDelta := ASourceRect.Right - ASourceSize.X;
    ASourceRect.Right := ASourceRect.Right - LDelta;
    ADestRect.Right := ADestRect.Right - (LDelta * LScale.X);
  end;

  if ASourceRect.Bottom > ASourceSize.Y then
  begin
    LDelta := ASourceRect.Bottom - ASourceSize.Y;
    ASourceRect.Bottom := ASourceRect.Bottom - LDelta;
    ADestRect.Bottom := ADestRect.Bottom - (LDelta * LScale.Y);
  end;

  if ADestRect.Left < 0 then
  begin
    LDelta := -ADestRect.Left;
    ADestRect.Left := ADestRect.Left + LDelta;
    ASourceRect.Left := ASourceRect.Left + (LDelta / LScale.X);
  end;

  if ADestRect.Top < 0 then
  begin
    LDelta := -ADestRect.Top;
    ADestRect.Top := ADestRect.Top + LDelta;
    ASourceRect.Top := ASourceRect.Top + (LDelta / LScale.Y);
  end;

  if ADestRect.Right > ADestSize.X then
  begin
    LDelta := ADestRect.Right - ADestSize.X;
    ADestRect.Right := ADestRect.Right - LDelta;
    ASourceRect.Right := ASourceRect.Right - (LDelta / LScale.X);
  end;

  if ADestRect.Bottom > ADestSize.Y then
  begin
    LDelta := ADestRect.Bottom - ADestSize.Y;
    ADestRect.Bottom := ADestRect.Bottom - LDelta;
    ASourceRect.Bottom := ASourceRect.Bottom - (LDelta / LScale.Y);
  end;

  Result := (not ASourceRect.Empty) and (not ADestRect.Empty);
end;

function FloatRect(const ALeft, ATop, AWidth, AHeight: VectorFloat): TFloatRect;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Width := AWidth;
  Result.Height := AHeight;
end;

function FloatRect(const AOrigin, ASize: TPoint2f): TFloatRect;
begin
  Result.TopLeft := AOrigin;
  Result.Size := ASize;
end;

function FloatRectBDS(const ALeft, ATop, ARight, ABottom: VectorFloat): TFloatRect;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Right := ARight;
  Result.Bottom := ABottom;
end;

function FloatRectBDS(const ATopLeft, ABottomRight: TPoint2f): TFloatRect;
begin
  Result.TopLeft := ATopLeft;
  Result.BottomRight := ABottomRight;
end;

{$ENDREGION}
{$REGION 'TQuad'}

class operator TQuad.Equal(const ARect1, ARect2: TQuad): Boolean;
begin
  Result := (ARect1.TopLeft = ARect2.TopLeft) and (ARect1.TopRight = ARect2.TopRight) and
    (ARect1.BottomRight = ARect2.BottomRight) and (ARect1.BottomLeft = ARect2.BottomLeft);
end;

class operator TQuad.NotEqual(const ARect1, ARect2: TQuad): Boolean;
begin
  Result := not (ARect1 = ARect2);
end;

function TQuad.Scale(const AScale: VectorFloat; const ACentered: Boolean): TQuad;
var
  LCenter: TPoint2f;
begin
  if Abs(AScale - 1.0) <= VectorEpsilon then
  begin
    Result := Self;
    Exit;
  end;

  if ACentered then
  begin
    LCenter := (TopLeft + TopRight + BottomRight + BottomLeft) * 0.25;
    Result.TopLeft := LCenter.Lerp(TopLeft, AScale);
    Result.TopRight := LCenter.Lerp(TopRight, AScale);
    Result.BottomRight := LCenter.Lerp(BottomRight, AScale);
    Result.BottomLeft := LCenter.Lerp(BottomLeft, AScale);
  end
  else
  begin
    Result.TopLeft := TopLeft * AScale;
    Result.TopRight := TopRight * AScale;
    Result.BottomRight := BottomRight * AScale;
    Result.BottomLeft := BottomLeft * AScale;
  end;
end;

function TQuad.Mirror: TQuad;
begin
  Result.TopLeft := TopRight;
  Result.TopRight := TopLeft;
  Result.BottomRight := BottomLeft;
  Result.BottomLeft := BottomRight;
end;

function TQuad.Flip: TQuad;
begin
  Result.TopLeft := BottomLeft;
  Result.TopRight := BottomRight;
  Result.BottomRight := TopRight;
  Result.BottomLeft := TopLeft;
end;

function TQuad.Transform(const AMatrix: TMatrix3x2f): TQuad;
begin
  Result.TopLeft := TopLeft * AMatrix;
  Result.TopRight := TopRight * AMatrix;
  Result.BottomRight := BottomRight * AMatrix;
  Result.BottomLeft := BottomLeft * AMatrix;
end;

function TQuad.Offset(const ADelta: TPoint2f): TQuad;
begin
  Result.TopLeft := TopLeft + ADelta;
  Result.TopRight := TopRight + ADelta;
  Result.BottomRight := BottomRight + ADelta;
  Result.BottomLeft := BottomLeft + ADelta;
end;

function TQuad.Offset(const ADeltaX, ADeltaY: VectorFloat): TQuad;
begin
  Result := Offset(Point2f(ADeltaX, ADeltaY));
end;

function TQuad.Contains(const APoint: TPoint2f): Boolean;
begin
  Result :=
    APoint.InsideTriangle(Values[0], Values[1], Values[2]) or
    APoint.InsideTriangle(Values[2], Values[3], Values[0]);
end;

class function TQuad.Scaled(const ALeft, ATop, AWidth, AHeight, AScale: VectorFloat;
  const ACentered: Boolean): TQuad;
var
  LNewLeft, LNewTop, LNewWidth, LNewHeight: VectorFloat;
begin
  if Abs(AScale - 1.0) <= VectorEpsilon then
  begin
    Result := Quad(ALeft, ATop, AWidth, AHeight);
    Exit;
  end;

  if ACentered then
  begin
    LNewWidth := AWidth * AScale;
    LNewHeight := AHeight * AScale;
    LNewLeft := ALeft + (AWidth - LNewWidth) * 0.5;
    LNewTop := ATop + (AHeight - LNewHeight) * 0.5;

    Result := Quad(LNewLeft, LNewTop, LNewWidth, LNewHeight);
  end
  else
    Result := Quad(ALeft, ATop, AWidth * AScale, AHeight * AScale);
end;

class function TQuad.Rotated(const ARotationOrigin, ASize, ARotationCenter: TPoint2f; const AAngle,
  AScale: VectorFloat): TQuad;
var
  LSinAngle, LCosAngle: VectorFloat;
  LScaled: Boolean;
  LNewPoint: TPoint2f;
  LIndex: Integer;
begin
  SinCos(AAngle, LSinAngle, LCosAngle);

  Result := Quad(-ARotationCenter.X, -ARotationCenter.Y, ASize.X, ASize.Y);

  LScaled := Abs(AScale - 1.0) > VectorEpsilon;

  for LIndex := 0 to High(Result.Values) do
  begin
    if LScaled then
      Result.Values[LIndex] := Result.Values[LIndex] * AScale;

    LNewPoint.X := Result.Values[LIndex].X * LCosAngle - Result.Values[LIndex].Y * LSinAngle;
    LNewPoint.Y := Result.Values[LIndex].Y * LCosAngle + Result.Values[LIndex].X * LSinAngle;

    Result.Values[LIndex] := LNewPoint + ARotationOrigin;
  end;
end;

class function TQuad.Rotated(const ARotationOrigin, ASize: TPoint2f; const AAngle, AScale: VectorFloat): TQuad;
begin
  Result := Rotated(ARotationOrigin, ASize, ASize * 0.5, AAngle, AScale);
end;

class function TQuad.RotatedTL(const ATopLeft, ASize, ARotationCenter: TPoint2f; const AAngle: VectorFloat;
  const AScale: VectorFloat): TQuad;
begin
  Result := Rotated(ATopLeft, ASize, ARotationCenter, AAngle, AScale).Offset(ARotationCenter);
end;

class function TQuad.SkewedHoriz(const AOrigin, ASize: TPoint2f;
  const AAngle: VectorFloat): TQuad;
var
  LOffset: VectorFloat;
begin
  LOffset := Cos(AAngle) * ASize.Y * 0.5;

  Result := Quad(FloatRect(AOrigin - ASize * 0.5, ASize));
  Result.TopLeft.X := Result.TopLeft.X - LOffset;
  Result.TopRight.X := Result.TopRight.X - LOffset;
  Result.BottomRight.X := Result.BottomRight.X + LOffset;
  Result.BottomLeft.X := Result.BottomLeft.X + LOffset;
end;

class function TQuad.SkewedVert(const AOrigin, ASize: TPoint2f;
  const AAngle: VectorFloat): TQuad;
var
  LOffset: VectorFloat;
begin
  LOffset := Cos(AAngle) * ASize.X * 0.5;

  Result := Quad(FloatRect(AOrigin - ASize * 0.5, ASize));
  Result.TopLeft.Y := Result.TopLeft.Y - LOffset;
  Result.TopRight.Y := Result.TopRight.Y - LOffset;
  Result.BottomRight.Y := Result.BottomRight.Y + LOffset;
  Result.BottomLeft.Y := Result.BottomLeft.Y + LOffset;
end;

function Quad(const ATopLeftX, ATopLeftY, ATopRightX, ATopRightY, ABottomRightX, ABottomRightY, ABottomLeftX,
  ABottomLeftY: VectorFloat): TQuad;
begin
  Result.TopLeft.X := ATopLeftX;
  Result.TopLeft.Y := ATopLeftY;
  Result.TopRight.X := ATopRightX;
  Result.TopRight.Y := ATopRightY;
  Result.BottomRight.X := ABottomRightX;
  Result.BottomRight.Y := ABottomRightY;
  Result.BottomLeft.X := ABottomLeftX;
  Result.BottomLeft.Y := ABottomLeftY;
end;

function Quad(const ATopLeft, ATopRight, ABottomRight, ABottomLeft: TPoint2f): TQuad;
begin
  Result.TopLeft := ATopLeft;
  Result.TopRight := ATopRight;
  Result.BottomRight := ABottomRight;
  Result.BottomLeft := ABottomLeft;
end;

function Quad(const ALeft, ATop, AWidth, AHeight: VectorFloat): TQuad;
begin
  Result.TopLeft.X := ALeft;
  Result.TopLeft.Y := ATop;
  Result.TopRight.X := ALeft + AWidth;
  Result.TopRight.Y := ATop;
  Result.BottomRight.X := Result.TopRight.X;
  Result.BottomRight.Y := ATop + AHeight;
  Result.BottomLeft.X := ALeft;
  Result.BottomLeft.Y := Result.BottomRight.Y;
end;

function Quad(const ARect: TFloatRect): TQuad;
begin
  Result.TopLeft := ARect.TopLeft;
  Result.TopRight := Point2f(ARect.Right, ARect.Top);
  Result.BottomRight := ARect.BottomRight;
  Result.BottomLeft := Point2f(ARect.Left, ARect.Bottom);
end;

function Quad(const ARect: TIntRect): TQuad;
begin
  Result.TopLeft := ARect.TopLeft;
  Result.TopRight := Point2f(ARect.Right, ARect.Top);
  Result.BottomRight := ARect.BottomRight;
  Result.BottomLeft := Point2f(ARect.Left, ARect.Bottom);
end;

{$ENDREGION}
{$REGION 'TMargins'}

class operator TMargins.Equal(const AMargins1, AMargins2: TMargins): Boolean;
begin
  Result := NearlyEqual(AMargins1.Left, AMargins2.Left) and NearlyEqual(AMargins1.Top, AMargins2.Top) and
    NearlyEqual(AMargins1.Right, AMargins2.Right) and NearlyEqual(AMargins1.Bottom, AMargins2.Bottom);
end;

class operator TMargins.NotEqual(const AMargins1, AMargins2: TMargins): Boolean;
begin
  Result := not (AMargins1 = AMargins2);
end;

function TMargins.Empty: Boolean;
begin
  Result := (Left < VectorEpsilon) and (Top < VectorEpsilon) and (Right < VectorEpsilon) and
    (Bottom < VectorEpsilon);
end;

function TMargins.GetHorizontal: VectorFloat;
begin
  Result := Left + Right;
end;

function TMargins.GetVertical: VectorFloat;
begin
  Result := Top + Bottom;
end;

function Margins(const ALeft, ATop, ARight, ABottom: VectorFloat): TMargins;
begin
  Result.Left := ALeft;
  Result.Top := ATop;
  Result.Right := ARight;
  Result.Bottom := ABottom;
end;

function Margins(const AHorizontal, AVertical: VectorFloat): TMargins; overload;
begin
  Result.Left := AHorizontal;
  Result.Top := AVertical;
  Result.Right := AHorizontal;
  Result.Bottom := AVertical;
end;

function Margins(const ARect: TFloatRect): TMargins; overload;
begin
  Result.Left := ARect.Left;
  Result.Top := ARect.Top;
  Result.Right := ARect.Right;
  Result.Bottom := ARect.Bottom;
end;

function Margins(const ATopLeft, ABottomRight: TPoint2f): TMargins; overload;
begin
  Result.Left := ATopLeft.X;
  Result.Top := ATopLeft.Y;
  Result.Right := ABottomRight.X;
  Result.Bottom := ABottomRight.Y;
end;

function Margins(const APadding: VectorFloat): TMargins; overload;
begin
  Result.Left := APadding;
  Result.Top := APadding;
  Result.Right := APadding;
  Result.Bottom := APadding;
end;

{$ENDREGION}
{$REGION 'TPathElement'}

class function TPathElement.Create(const AValue: Single): TPathElement;
begin
  Result.Value := AValue;
end;

class function TPathElement.Create(const ACommand: Byte; const ALength: Word): TPathElement;
begin
  Result.Command := ACommand;
  Result.Length := ALength;
end;

{$ENDREGION}
{$REGION 'TPathBuilder'}

constructor TPathBuilder.Create;
begin
  inherited;
  FElements := TPathElements.Create;
  FLastCommand := $FF;
  FLastIndex := -1;
end;

destructor TPathBuilder.Destroy;
begin
  FElements.Free;
  inherited;
end;

function TPathBuilder.GetElements: TPathElementArray;
begin
  Result := FElements.ToArray;
end;

procedure TPathBuilder.PassCommandPoints(const ACommand: Byte; const APoints: array of TPoint2f;
  const ARelative: Boolean);
var
  LCommand: Byte;
  LValueCount, I: Integer;
begin
  LCommand := ACommand;
  if ARelative then
    LCommand := LCommand or TPathCommand.Relative;

  LValueCount := Length(APoints) * 2;

  if (FLastIndex = -1) or (FLastCommand <> LCommand) or
    (FElements[FLastIndex].Length >= High(Word) - LValueCount) then
  begin // Create a new command.
    FElements.Add(TPathElement.Create(LCommand, LValueCount));

    for I := 0 to Length(APoints) - 1 do
    begin
      FElements.Add(TPathElement.Create(APoints[I].X));
      FElements.Add(TPathElement.Create(APoints[I].Y));
    end;

    FLastCommand := LCommand;
    FLastIndex := FElements.Count - (LValueCount + 1);
  end
  else
  begin // Add additional data to an existing command.
    for I := 0 to Length(APoints) - 1 do
    begin
      FElements.Add(TPathElement.Create(APoints[I].X));
      FElements.Add(TPathElement.Create(APoints[I].Y));
    end;
    FElements[FLastIndex] := TPathElement.Create(FLastCommand, FElements[FLastIndex].Length + LValueCount);
  end;
end;

procedure TPathBuilder.Clear;
begin
  FElements.Clear;
  FLastCommand := $FF;
  FLastIndex := -1;
end;

procedure TPathBuilder.ClosePath;
begin
  if (FLastIndex <> -1) and (FLastCommand <> TPathCommand.Close) then
  begin
    FLastIndex := FElements.Add(TPathElement.Create(TPathCommand.Close));
    FLastCommand := TPathCommand.Close;
  end;
end;

procedure TPathBuilder.MoveTo(const APosition: TPoint2f; const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.MoveTo, [APosition], ARelative);
end;

procedure TPathBuilder.LineTo(const APosition: TPoint2f; const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.LineTo, [APosition], ARelative);
end;

procedure TPathBuilder.CurveTo(const AControlPoint1, AControlPoint2, APosition: TPoint2f;
  const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.CurveTo, [AControlPoint1, AControlPoint2, APosition], ARelative);
end;

procedure TPathBuilder.SmoothCurveTo(const AControlPoint2, APosition: TPoint2f; const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.CurveTo or TPathCommand.Smooth, [AControlPoint2, APosition], ARelative);
end;

procedure TPathBuilder.QuadCurveTo(const AControlPoint, APosition: TPoint2f; const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.QuadCurveTo, [AControlPoint, APosition], ARelative);
end;

procedure TPathBuilder.SmoothQuadCurveTo(const APosition: TPoint2f; const ARelative: Boolean);
begin
  PassCommandPoints(TPathCommand.QuadCurveTo or TPathCommand.Smooth, [APosition], ARelative);
end;

procedure TPathBuilder.Rectangle(const ARectangle: TFloatRect; const AClockwise: Boolean;
  const ARelative: Boolean);
begin
  MoveTo(ARectangle.TopLeft, ARelative);
  if AClockwise then
  begin
    LineTo(Point2f(ARectangle.Width, 0.0), True);
    LineTo(Point2f(0.0, ARectangle.Height), True);
    LineTo(Point2f(-ARectangle.Width, 0.0), True);
    LineTo(Point2f(0.0, -ARectangle.Height), True);
  end
  else
  begin
    LineTo(Point2f(0.0, ARectangle.Height), True);
    LineTo(Point2f(ARectangle.Width, 0.0), True);
    LineTo(Point2f(0.0, -ARectangle.Height), True);
    LineTo(Point2f(-ARectangle.Width, 0.0), True);
  end;
  ClosePath;
end;

procedure TPathBuilder.Circle(const ACenter: TPoint2f; const ARadius: Single; const AClockwise,
  ARelative: Boolean);
const
  Theta: Single = 0.551915;
begin
  if ARadius > 0.0 then
  begin
    if AClockwise then
    begin
      MoveTo(Point2f(ACenter.X, ACenter.Y - ARadius), ARelative);
      CurveTo(Point2f(ARadius * Theta, 0.0), Point2f(ARadius, ARadius - ARadius * Theta),
        Point2f(ARadius, ARadius), True);
      SmoothCurveTo(Point2f(ARadius * Theta - ARadius, ARadius), Point2f(-ARadius, ARadius), True);
      SmoothCurveTo(Point2f(-ARadius, ARadius * Theta - ARadius), Point2f(-ARadius, -ARadius), True);
      SmoothCurveTo(Point2f(ARadius - ARadius * Theta, -ARadius), Point2f(ARadius, -ARadius), True);
    end
    else
    begin
      MoveTo(Point2f(ACenter.X, ACenter.Y + ARadius), ARelative);
      CurveTo(Point2f(ARadius * Theta, 0.0), Point2f(ARadius, ARadius * Theta - ARadius),
        Point2f(ARadius, -ARadius), True);
      SmoothCurveTo(Point2f(ARadius * Theta - ARadius, -ARadius), Point2f(-ARadius, -ARadius), True);
      SmoothCurveTo(Point2f(-ARadius, ARadius - ARadius * Theta), Point2f(-ARadius, ARadius), True);
      SmoothCurveTo(Point2f(ARadius - ARadius * Theta, ARadius), Point2f(ARadius, ARadius), True);
    end;
  end;
  ClosePath();
end;

procedure TPathBuilder.Diamond(const ACenter: TPoint2f; const ARadius: Single; const AClockwise,
  ARelative: Boolean);
begin
  if ARadius > 0.0 then
  begin
    if AClockwise then
    begin
      MoveTo(Point2f(ACenter.X, ACenter.Y - ARadius), ARelative);
      LineTo(Point2f(ARadius, ARadius), True);
      LineTo(Point2f(-ARadius, ARadius), True);
      LineTo(Point2f(-ARadius, -ARadius), True);
    end
    else
    begin
      MoveTo(Point2f(ACenter.X, ACenter.Y + ARadius), ARelative);
      LineTo(Point2f(ARadius, -ARadius), True);
      LineTo(Point2f(-ARadius, -ARadius), True);
      LineTo(Point2f(-ARadius, ARadius), True);
    end;
  end;
  ClosePath;
end;

procedure TPathBuilder.Gear(const ACenter: TPoint2f; const ARadius, AAmplitude: Single; const ASteps,
  ATeeth: Integer; const AClockwise: Boolean);
var
  I: Integer;
  LTheta, LAngle, LDistance: Single;
  LPosition: TPoint2f;
begin
  if ARadius > 0.0 then
  begin
    for I := 0 to ASteps - 1 do
    begin
      if AClockwise then
        LTheta := I / ASteps
      else
        LTheta := 1.0 - (I / ASteps);

      LAngle := LTheta * Pi * 2.0;
      LDistance := ARadius + SineTransform(SineCycle(LTheta * ATeeth)) * AAmplitude;
      LPosition := Point2f(
        ACenter.X + Cos(LAngle) * LDistance,
        ACenter.Y - Sin(LAngle) * LDistance);

      if (I = 0) then
        MoveTo(LPosition)
      else
        LineTo(LPosition);
    end;
  end;
  ClosePath;
end;

{$ENDREGION}

end.