Files
BABA_YAGA/Assets/Opsive/UltimateCharacterController/Scripts/Utility/MathUtility.cs
2026-06-09 02:05:00 +07:00

573 lines
31 KiB
C#

/// ---------------------------------------------
/// Ultimate Character Controller
/// Copyright (c) Opsive. All Rights Reserved.
/// https://www.opsive.com
/// ---------------------------------------------
namespace Opsive.UltimateCharacterController.Utility
{
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// A collection of math functions.
/// </summary>
public class MathUtility
{
// The multiplier when converting from the CharacterLocomotion force to a Rigidbody force.
public const float RigidbodyForceMultiplier = 50;
private static Dictionary<Collider, Transform> m_ColliderTransformMap = new Dictionary<Collider, Transform>();
/// <summary>
/// Returns the friction value between material1 and material2.
/// </summary>
/// <param name="material1">The first material to get the friction value of.</param>
/// <param name="material2">The second material to get the friction value of.</param>
/// <returns>The combined friction value.</returns>
public static float FrictionValue(PhysicsMaterial material1, PhysicsMaterial material2, bool dynamicFriction)
{
if (material1.frictionCombine == PhysicsMaterialCombine.Maximum || material2.frictionCombine == PhysicsMaterialCombine.Maximum) {
return dynamicFriction ? Mathf.Max(material1.dynamicFriction, material2.dynamicFriction) : Mathf.Max(material1.staticFriction, material2.staticFriction);
}
if (material1.frictionCombine == PhysicsMaterialCombine.Minimum || material2.frictionCombine == PhysicsMaterialCombine.Minimum) {
return dynamicFriction ? Mathf.Min(material1.dynamicFriction, material2.dynamicFriction) : Mathf.Min(material1.staticFriction, material2.staticFriction);
}
if (material1.frictionCombine == PhysicsMaterialCombine.Multiply || material2.frictionCombine == PhysicsMaterialCombine.Multiply) {
return dynamicFriction ? (material1.dynamicFriction * material2.dynamicFriction) : (material1.staticFriction * material2.staticFriction);
}
return dynamicFriction ? ((material1.dynamicFriction + material2.dynamicFriction) / 2) : ((material1.staticFriction + material2.staticFriction) / 2); // Average combine.
}
/// <summary>
/// Returns the bounciness value between material1 and material2.
/// </summary>
/// <param name="material1">The first material to get the bounciness value of.</param>
/// <param name="material2">The second material to get the bounciness value of.</param>
/// <returns>The combined bounciness value.</returns>
public static float BouncinessValue(PhysicsMaterial material1, PhysicsMaterial material2)
{
if (material1.bounceCombine == PhysicsMaterialCombine.Maximum || material2.bounceCombine == PhysicsMaterialCombine.Maximum) {
return Mathf.Max(material1.bounciness, material2.bounciness);
}
if (material1.bounceCombine == PhysicsMaterialCombine.Minimum || material2.bounceCombine == PhysicsMaterialCombine.Minimum) {
return Mathf.Min(material1.bounciness, material2.bounciness);
}
if (material1.bounceCombine == PhysicsMaterialCombine.Multiply || material2.bounceCombine == PhysicsMaterialCombine.Multiply) {
return (material1.bounciness * material2.bounciness);
}
return (material1.bounciness + material2.bounciness) / 2; // Average combine.
}
/// <summary>
/// Transforms the position from local space to world space. This is similar to Transform.TransformPoint but does not require a Transform.
/// </summary>
/// <param name="worldPosition">The world position of the object.</param>
/// <param name="rotation">The world rotation of the object.</param>
/// <param name="localPosition">The local position of the object</param>
/// <returns>The world space position.</returns>
public static Vector3 TransformPoint(Vector3 worldPosition, Quaternion rotation, Vector3 localPosition)
{
return worldPosition + (rotation * localPosition);
}
/// <summary>
/// Transforms the position from world space to local space. This is similar to Transform.InverseTransformPoint but does not require a Transform.
/// </summary>
/// <param name="worldPosition">The world position of the object.</param>
/// <param name="rotation">The world rotation of the object.</param>
/// <param name="position">The position of the object.</param>
/// <returns>The local space position.</returns>
public static Vector3 InverseTransformPoint(Vector3 worldPosition, Quaternion rotation, Vector3 position)
{
var diff = position - worldPosition;
return Quaternion.Inverse(rotation) * diff;
}
/// <summary>
/// Transforms the direction from local space to world space. This is similar to Transform.TransformDirection but does not require a Transform.
/// </summary>
/// <param name="direction">The direction to transform from local space to world space.</param>
/// <param name="rotation">The world rotation of the object.</param>
/// <returns>The world space direction.</returns>
public static Vector3 TransformDirection(Vector3 direction, Quaternion rotation)
{
return rotation * direction;
}
/// <summary>
/// Transforms the direction from world space to local space. This is similar to Transform.InverseTransformDirection but does not require a Transform.
/// </summary>
/// <param name="direction">The direction to transform from world space to local space.</param>
/// <param name="rotation">The world rotation of the object.</param>
/// <returns>The local space direction.</returns>
public static Vector3 InverseTransformDirection(Vector3 direction, Quaternion rotation)
{
return Quaternion.Inverse(rotation) * direction;
}
/// <summary>
/// Transforms the rotation from local space to world space.
/// </summary>
/// <param name="worldRotation">The world rotation of the object.</param>
/// <param name="rotation">The rotation to transform from local space to world space.</param>
/// <returns>The world space rotation.</returns>
public static Quaternion TransformQuaternion(Quaternion worldRotation, Quaternion rotation)
{
return worldRotation * rotation;
}
/// <summary>
/// Transforms the rotation from world space to local space.
/// </summary>
/// <param name="worldRotation">The world rotation of the object.</param>
/// <param name="rotation">The rotation to transform from world space to local space.</param>
/// <returns>The local space rotation.</returns>
public static Quaternion InverseTransformQuaternion(Quaternion worldRotation, Quaternion rotation)
{
return Quaternion.Inverse(worldRotation) * rotation;
}
/// <summary>
/// Determines the endcaps of a capsule.
/// </summary>
/// <param name="capsuleCollider">The CapsuleCollider to determine the endcaps of.</param>
/// <param name="position">The position of the CapsuleCollider's transform.</param>
/// <param name="rotation">The rotation of the CapsuleCollider's transform.</param>
/// <param name="firstEndCap">The first resulting endcap.</param>
/// <param name="secondEndCap">The second resulting endcap.</param>
public static void CapsuleColliderEndCaps(CapsuleCollider capsuleCollider, Vector3 position, Quaternion rotation, out Vector3 firstEndCap, out Vector3 secondEndCap)
{
var direction = CapsuleColliderDirection(capsuleCollider);
var heightMultiplier = CapsuleColliderHeightMultiplier(capsuleCollider);
var radiusMultipler = ColliderRadiusMultiplier(capsuleCollider);
firstEndCap = TransformPoint(position, rotation, Vector3.Scale(capsuleCollider.center, capsuleCollider.transform.lossyScale) + direction * (-(capsuleCollider.height * heightMultiplier * 0.5f) + capsuleCollider.radius * radiusMultipler));
secondEndCap = firstEndCap + (rotation * direction) * (capsuleCollider.height * heightMultiplier - capsuleCollider.radius * radiusMultipler * 2);
}
/// <summary>
/// Determines the endcaps of a capsule.
/// </summary>
/// <param name="height">The height of the CapsuleCollider.</param>
/// <param name="radius">The radius of the CapsuleCollider.</param>
/// <param name="center">The center of the CapsuleCollider.</param>
/// <param name="direction">The direction of the CapsuleCollider.</param>
/// <param name="position">The position of the CapsuleCollider's transform.</param>
/// <param name="rotation">The rotation of the CapsuleCollider's transform.</param>
/// <param name="firstEndCap">The first resulting endcap.</param>
/// <param name="secondEndCap">The second resulting endcap.</param>
public static void CapsuleColliderEndCaps(float height, float radius, Vector3 center, Vector3 direction, Vector3 position, Quaternion rotation, out Vector3 firstEndCap, out Vector3 secondEndCap)
{
firstEndCap = TransformPoint(position, rotation, center + direction * (-(height * 0.5f) + radius));
secondEndCap = firstEndCap + (rotation * direction) * (height - radius * 2);
}
/// <summary>
/// Returns the world direction that the CapsuleCollider is facing.
/// </summary>
/// <param name="capsuleCollider">The CapsuleCollider to determine the direction that it is facing.</param>
/// <returns>The world direction of the CapsuleCollider.</returns>
public static Vector3 CapsuleColliderDirection(CapsuleCollider capsuleCollider)
{
Vector3 direction;
if (capsuleCollider.direction == 1) { // Y-Axis.
direction = Vector3.up;
} else if (capsuleCollider.direction == 2) { // Z-Axis.
direction = Vector3.forward;
} else { // X-Axis.
direction = Vector3.right;
}
return direction;
}
/// <summary>
/// Clamp the angle between -180 and 180 degrees.
/// </summary>
/// <param name="angle">The angle to clamp.</param>
/// <returns>An angle between -180 and 180 degrees.</returns>
public static float ClampInnerAngle(float angle)
{
if (angle < -180) {
angle += 360;
}
if (angle > 180) {
angle -= 360;
}
return angle;
}
/// <summary>
/// Clamp the angle between 0 and 360 degrees.
/// </summary>
/// <param name="angle">The angle to clamp.</param>
/// <returns>An angle between 0 and 360 degrees.</returns>
public static float ClampAngle(float angle)
{
if (angle < 0) {
angle += 360;
}
if (angle > 360) {
angle -= 360;
}
return angle;
}
/// <summary>
/// Clamp the angle between min and max degrees.
/// </summary>
/// <param name="angle">The angle to clamp.</param>
/// <param name="min">The minimum angle range.</param>
/// <param name="max">The maximum angle range.</param>
/// <returns>An angle between min and max degrees.</returns>
public static float ClampAngle(float angle, float min, float max)
{
var minDiff = ClampInnerAngle(min - angle);
var maxDiff = ClampInnerAngle(angle - max);
if (Mathf.Abs(minDiff) < Mathf.Abs(maxDiff)) {
if (minDiff <= 0) {
return angle;
}
return min;
}
if (maxDiff <= 0) {
return angle;
}
return max;
}
/// <summary>
/// Clamp the angle between min and max degrees.
/// </summary>
/// <param name="angle">The original angle to clamp.</param>
/// <param name="deltaAngle">The angle to add to the original angle.</param>
/// <param name="min">The minimum angle range.</param>
/// <param name="max">The maximum angle range.</param>
/// <returns>An angle between min and max degrees.</returns>
public static float ClampAngle(float angle, float deltaAngle, float min, float max)
{
var minDiff = ClampInnerAngle(min - angle);
var maxDiff = ClampInnerAngle(angle - max);
if (Mathf.Abs(minDiff) < Mathf.Abs(maxDiff)) {
if (ClampInnerAngle(min - (angle + deltaAngle)) <= 0) {
return (angle + deltaAngle);
}
return min;
}
if (ClampInnerAngle((angle + deltaAngle) - max) <= 0) {
return (angle + deltaAngle);
}
return max;
}
/// <summary>
/// Returns the rotation of the specified matrix.
/// </summary>
/// <param name="matrix">The matrix to get the rotation of.</param>
/// <returns>The rotation of the specified matrix.</returns>
public static Quaternion QuaternionFromMatrix(Matrix4x4 matrix)
{
return Quaternion.LookRotation(matrix.GetColumn(2), matrix.GetColumn(1));
}
/// <summary>
/// Returns the position of the specified matrix.
/// </summary>
/// <param name="matrix">The matrix to get the position of.</param>
/// <returns>The rotation of the specified matrix.</returns>
public static Vector3 PositionFromMatrix(Matrix4x4 matrix)
{
return matrix.GetColumn(3);
}
/// <summary>
/// Returns the matrix of the first Transform with the deltaRotation applied to the root Transform. This is similar to calling Transform.rotation = value on
/// the root Transform and getting the position/rotation of the child.
/// </summary>
/// <param name="current">The current Transform to add to the matrix.</param>
/// <param name="root">The base Transform that should have the delta rotation applied to.</param>
/// <param name="deltaRotation">The rotation to apply to the root Transform.</param>
/// <returns>The matrix of the first Transform with the deltaRotation applied to the root Transform.</returns>
public static Matrix4x4 ApplyRotationToChildMatrices(Transform current, Transform root, Quaternion deltaRotation)
{
// Recursively multiply the matrices as long as the current Transform is not at the root.
if (current != root) {
return ApplyRotationToChildMatrices(current.parent, root, deltaRotation) * Matrix4x4.TRS(current.localPosition, current.localRotation, current.localScale);
}
// At the root of the tree, apply the delta to the rotation and return the matrix.
return Matrix4x4.TRS(current.localPosition, current.localRotation * deltaRotation, current.localScale);
}
/// <summary>
/// Returns the closest point on a capsule or sphere collider.
/// </summary>
/// <param name="transform">The parent transform of the object which contains the collider.</param>
/// <param name="collider">The collider to get the closest point of.</param>
/// <param name="point">The point used to find the closest point on the collider.</param>
/// <param name="moveDirection">The direction that the character is moving.</param>
/// <param name="sphereCheck">Should a sphere check be performed? If false the Pythagorean theorem will be used.</param>
/// <param name="lowerPoint">Should the lower point of the collider be returned? Used by the ground check to always return the lowest point.</param>
/// <returns>The closest point on the collider.</returns>
public static Vector3 ClosestPointOnCollider(Transform transform, Collider collider, Vector3 point, Vector3 moveDirection, bool sphereCheck, bool lowerPoint)
{
if (collider is CapsuleCollider) {
return ClosestPointOnCapsule(transform, collider as CapsuleCollider, point, moveDirection, sphereCheck, lowerPoint);
} else { // SphereCollider.
var sphereCollider = collider as SphereCollider;
return ClosestPointOnSphere(transform, point, collider.transform.TransformPoint(sphereCollider.center), sphereCollider.radius * ColliderRadiusMultiplier(collider), sphereCheck, lowerPoint);
}
}
/// <summary>
/// Returns the closest point on a CapsuleCollider.
/// </summary>
/// <param name="transform">The parent transform of the object which contains the collider.</param>
/// <param name="capsuleCollider">The CapsuleCollider to get the closest point of.</param>
/// <param name="point">The point used to find the closest point on the collider.</param>
/// <param name="moveDirection">The direction that the character is moving.</param>
/// <param name="sphereCheck">Should a sphere check be performed? If false the Pythagorean theorem will be used.</param>
/// <param name="lowerPoint">Should the lower point of the collider be returned? Used by the ground check to always return the lowest point.</param>
/// <returns>The closest point on a capsule.</returns>
private static Vector3 ClosestPointOnCapsule(Transform transform, CapsuleCollider capsuleCollider, Vector3 point, Vector3 moveDirection, bool sphereCheck, bool lowerPoint)
{
Vector3 capsuleDirection;
if (capsuleCollider.direction == 1) { // Y-Axis.
capsuleDirection = capsuleCollider.transform.up;
} else if (capsuleCollider.direction == 2) { // Z-Axis.
capsuleDirection = capsuleCollider.transform.forward;
} else { // X-Axis.
capsuleDirection = capsuleCollider.transform.right;
}
var heightMultiplier = CapsuleColliderHeightMultiplier(capsuleCollider);
var radiusMultiplier = ColliderRadiusMultiplier(capsuleCollider);
// If the hit point is within the spheres of the Capsule Collider then the collider position should be based off of the Capsule Collider length (using the point projected onto
// a cylinder forumla). If the hit point is on the ends of the Capsule Collider then the Calsule Collider caps should be used (or, based off of a sphere).
var capsuleCenter = capsuleCollider.transform.TransformPoint(capsuleCollider.center) + moveDirection;
var capsuleLength = ((capsuleCollider.height * heightMultiplier * 0.5f) - capsuleCollider.radius * radiusMultiplier);
var start = capsuleCenter - capsuleDirection * capsuleLength;
var end = capsuleCenter + capsuleDirection * capsuleLength;
var hitDirection = (point - capsuleCenter).normalized;
// Use the project point on segment forumla to determine if the closest point is on the segment or the endcap.
var pointStartDirection = point - start;
var endStartDirection = end - start;
var segment = (Vector3.Dot(pointStartDirection, endStartDirection) / Vector3.Dot(endStartDirection, endStartDirection));
if (segment >= 0 && segment <= 1) { // On cylinder.
// If the point is on the segment then the collision point is within the collider.
var closestPoint = start + segment * endStartDirection;
var pointDirection = (point - closestPoint).normalized * capsuleCollider.radius * radiusMultiplier;
if (lowerPoint) {
// If the direction is above the collider then inverse the direction. This will prevent the closest point being on top of the collider when it should be on the bottom.
var localCylinderDirection = transform.InverseTransformDirection(pointDirection);
if (localCylinderDirection.y > 0) {
localCylinderDirection.y *= -1;
pointDirection = transform.TransformDirection(localCylinderDirection);
}
}
return closestPoint + pointDirection;
} else { // On sphere.
if (lowerPoint) {
// If the direction is above the collider then inverse the direction. This will prevent the closest point being on top of the collider when it should be on the bottom.
var localHitDirection = transform.InverseTransformDirection(hitDirection);
if (localHitDirection.y > 0) {
localHitDirection.y *= -1;
hitDirection = transform.TransformDirection(localHitDirection);
}
}
var dot = Vector3.Dot(capsuleDirection, hitDirection);
var sphereCenter = capsuleCenter + (capsuleDirection * capsuleLength * Mathf.Sign(dot));
return ClosestPointOnSphere(transform, point, sphereCenter, capsuleCollider.radius * radiusMultiplier, sphereCheck, lowerPoint);
}
}
/// <summary>
/// Returns the closest point on a SphereCollider.
/// </summary>
/// <param name="transform">The parent transform of the object which contains the collider.</param>
/// <param name="point">The point used to find the closest point on the collider.</param>
/// <param name="sphereCenter">The center of the sphere.</param>
/// <param name="radius">The radius of the sphere.</param>
/// <param name="sphereCheck">Should a sphere check be performed? If false the Pythagorean theorem will be used.</param>
/// <returns>The closest point on a sphere.</returns>
private static Vector3 ClosestPointOnSphere(Transform transform, Vector3 point, Vector3 sphereCenter, float radius, bool sphereCheck, bool lowerPoint)
{
var position = Vector3.zero;
var localDirection = InverseTransformPoint(sphereCenter, transform.rotation, point);
if (sphereCheck || localDirection.y > radius) {
// Use the standard closest point on a sphere algorithm.
var direction = (point - sphereCenter).normalized;
if (lowerPoint) {
// If the direction is above the collider then inverse the direction. This will prevent the closest point being on top of the collider when it should be on the bottom.
var localSphereDirection = transform.InverseTransformDirection(direction);
if (localSphereDirection.y > 0) {
localSphereDirection.y *= -1;
direction = transform.TransformDirection(localSphereDirection);
}
}
position = sphereCenter + (direction * radius);
} else {
// Use the Pythagorean theorem to determine the point. This won't return the closest point but it will return the point that the collider should adjust to.
// Ignore the local y value because the Pythagorean theorem is used to determine the y position.
localDirection.y = 0;
var magnitude = localDirection.magnitude;
if (magnitude < radius) {
position = sphereCenter - transform.up * Mathf.Sqrt((radius * radius) - (magnitude * magnitude));
} else {
position = sphereCenter - transform.up * radius;
}
}
return position;
}
/// <summary>
/// Returns the CapsuleCollider height multipler based off of the scale.
/// </summary>
/// <param name="capsuleCollider">The CapsuleCollider to determine the height multiplier of.</param>
/// <returns>The capsule collider height multipler based off of the scale.</returns>
public static float CapsuleColliderHeightMultiplier(CapsuleCollider capsuleCollider)
{
// Use the cached transform for quick lookup.
Transform transform;
if (!m_ColliderTransformMap.TryGetValue(capsuleCollider, out transform)) {
transform = capsuleCollider.transform;
m_ColliderTransformMap.Add(capsuleCollider, transform);
}
if (capsuleCollider.direction == 1) { // Y-axis.
return transform.lossyScale.y;
} else if (capsuleCollider.direction == 2) { // Z-axis.
return transform.lossyScale.z;
}
return transform.lossyScale.x;
}
/// <summary>
/// Returns the radius multipler of the collider based off of the scale.
/// </summary>
/// <param name="collider">The collider determine the radius multiplier of.</param>
/// <returns>The radius multipler of the collider based off of the scale.</returns>
public static float ColliderRadiusMultiplier(Collider collider)
{
// Use the cached transform for quick lookup.
Transform transform;
if (!m_ColliderTransformMap.TryGetValue(collider, out transform)) {
transform = collider.transform;
m_ColliderTransformMap.Add(collider, transform);
}
var lossyScale = transform.lossyScale;
if (collider is CapsuleCollider) {
var capsuleCollider = collider as CapsuleCollider;
if (capsuleCollider.direction == 1) { // Y-axis.
return Mathf.Max(lossyScale.x, lossyScale.z);
} else if (capsuleCollider.direction == 2) { // Z-axis.
return Mathf.Max(lossyScale.x, lossyScale.y);
}
return Mathf.Max(lossyScale.y, lossyScale.z);
} else { // SphereCollider.
return Mathf.Max(lossyScale.x, Mathf.Max(lossyScale.y, lossyScale.z));
}
}
/// <summary>
/// Is the point under the Collider?
/// </summary>
/// <param name="transform">The Collider's Transform.</param>
/// <param name="collider">The interested Collider.</param>
/// <param name="point">The point to check if under the Collider.</param>
/// <returns>Returns true if the point is under the Collider.</returns>
public static bool IsUnderCollider(Transform transform, Collider collider, Vector3 point)
{
var center = (collider is SphereCollider ? (collider as SphereCollider).center : (collider as CapsuleCollider).center);
var direction = transform.InverseTransformDirection(point - collider.transform.TransformPoint(center));
return direction.y <= 0.001f;
}
/// <summary>
/// Returns the height of the collider.
/// </summary>
/// <param name="transform">The transform used to determine the up direction.</param>
/// <param name="collider">The collider to get the height of.</param>
/// <returns>The height of the collider.</returns>
public static float LocalColliderHeight(Transform transform, Collider collider)
{
// The height of the collider is determined by the uppermost point on the collider transformed into the local position of the object.
var maxValue = (collider is CapsuleCollider ? (collider as CapsuleCollider).height : (collider as SphereCollider).radius) * 100;
var topPosition = ClosestPointOnCollider(transform, collider, transform.TransformPoint(0, maxValue, 0), Vector3.zero, true, false);
return transform.InverseTransformPoint(topPosition).y;
}
/// <summary>
/// Returns the invese of pow.
/// </summary>
/// <param name="b">The base value.</param>
/// <param name="value">The value computed by pow.</param>
/// <returns>The inverse of pow.</returns>
public static float InversePow(float b, float value)
{
return Mathf.Log(value) / Mathf.Log(b);
}
/// <summary>
/// Rounds the specified value according to the number of decimals.
/// </summary>
/// <param name="value">The value to round.</param>
/// <param name="factor">The factor to round to.</param>
/// <returns>The roudned value.</returns>
public static float Round(float value, int factor)
{
return Mathf.Round(value * factor) / factor;
}
/// <summary>
/// Rounds the specified value according to the number of decimals.
/// </summary>
/// <param name="value">The value to round.</param>
/// <param name="factor">The factor to round to.</param>
/// <returns>The roudned value.</returns>
public static Quaternion Round(Quaternion value, int factor)
{
value.x = Round(value.x, factor);
value.y = Round(value.y, factor);
value.z = Round(value.z, factor);
value.w = Round(value.w, factor);
return value;
}
/// <summary>
/// Retrusn true if the specified scale is almost uniform. An epsilon value is used so the scale doesn't have to be
/// precisely uniform.
/// </summary>
/// <param name="scale">The scale to determine if it is uniform.</param>
/// <returns>True if the specified scale is almost uniform.</returns>
public static bool IsUniform(Vector3 scale)
{
return Mathf.Abs(scale.x - scale.y) < 0.00001f && Mathf.Abs(scale.y - scale.z) < 0.00001f;
}
/// <summary>
/// Returns true if layer is within the layerMask.
/// </summary>
/// <param name="layer">The layer to check.</param>
/// <param name="layerMask">The mask to compare against.</param>
/// <returns>True if the layer is within the layer mask.</returns>
public static bool InLayerMask(int layer, int layerMask)
{
return ((1 << layer) & layerMask) == (1 << layer);
}
/// <summary>
/// Concatenate three integers into one. The first int will occupy the millions place, the second int will occupy the thousands place, and the third
/// int will occupy the hundeds place. For example, a value of 30, 52, 1 will return 30052001.
/// </summary>
/// <param name="a">The first integer to concatentate.</param>
/// <param name="a">The second integer to concatentate.</param>
/// <param name="a">The third integer to concatentate.</param>
/// <returns>The concatenated integer.</returns>
public static int Concatenate(int a, int b, int c)
{
return (a * 1000000) + (b * 1000) + c;
}
}
}