mirror of
https://git.do.srb2.org/STJr/SRB2.git
synced 2024-11-15 09:11:48 +00:00
542 lines
16 KiB
C
542 lines
16 KiB
C
// SONIC ROBO BLAST 2
|
|
//-----------------------------------------------------------------------------
|
|
// Copyright (C) 1993-1996 by id Software, Inc.
|
|
// Copyright (C) 1998-2000 by DooM Legacy Team.
|
|
// Copyright (C) 1999-2023 by Sonic Team Junior.
|
|
//
|
|
// This program is free software distributed under the
|
|
// terms of the GNU General Public License, version 2.
|
|
// See the 'LICENSE' file for more details.
|
|
//-----------------------------------------------------------------------------
|
|
/// \file tables.c
|
|
/// \brief Lookup tables
|
|
/// Do not try to look them up :-).
|
|
|
|
// In the order of appearance:
|
|
|
|
// fixed_t finetangent[4096] - Tangents LUT.
|
|
// Should work with BAM fairly well (12 of 16bit, effectively, by shifting).
|
|
|
|
// fixed_t finesine[10240] - Sine lookup.
|
|
// Guess what, serves as cosine, too.
|
|
// Remarkable thing is, how to use BAMs with this?
|
|
|
|
// fixed_t tantoangle[2049] - ArcTan LUT,
|
|
// Maps tan(angle) to angle fast. Gotta search.
|
|
|
|
#include "tables.h"
|
|
|
|
unsigned SlopeDiv(unsigned num, unsigned den)
|
|
{
|
|
unsigned ans;
|
|
num <<= (FINE_FRACBITS-FRACBITS);
|
|
den <<= (FINE_FRACBITS-FRACBITS);
|
|
if (den < 512)
|
|
return SLOPERANGE;
|
|
ans = (num<<3) / (den>>8);
|
|
return ans <= SLOPERANGE ? ans : SLOPERANGE;
|
|
}
|
|
|
|
UINT64 SlopeDivEx(unsigned int num, unsigned int den)
|
|
{
|
|
UINT64 ans;
|
|
if (den < 512)
|
|
return SLOPERANGE;
|
|
ans = ((UINT64)num<<3)/(den>>8);
|
|
return ans <= SLOPERANGE ? ans : SLOPERANGE;
|
|
}
|
|
|
|
fixed_t AngleFixed(angle_t af)
|
|
{
|
|
angle_t wa = ANGLE_180;
|
|
fixed_t wf = 180*FRACUNIT;
|
|
fixed_t rf = 0*FRACUNIT;
|
|
|
|
while (af)
|
|
{
|
|
while (af < wa)
|
|
{
|
|
wa /= 2;
|
|
wf /= 2;
|
|
}
|
|
rf += wf;
|
|
af -= wa;
|
|
}
|
|
|
|
return rf;
|
|
}
|
|
|
|
static FUNCMATH angle_t AngleAdj(const fixed_t fa, const fixed_t wf,
|
|
angle_t ra)
|
|
{
|
|
const angle_t adj = 0x77;
|
|
const boolean fan = fa < 0;
|
|
const fixed_t sl = FixedDiv(fa, wf*2);
|
|
const fixed_t lb = fa % (wf*2);
|
|
const fixed_t lo = (wf*2)-lb;
|
|
|
|
if (ra == 0)
|
|
{
|
|
if (lb == 0)
|
|
{
|
|
ra = FixedMul(FRACUNIT/512, sl);
|
|
if (ra > FRACUNIT/64)
|
|
return InvAngle(ra);
|
|
return ra;
|
|
}
|
|
else if (lb > 0)
|
|
return InvAngle(FixedMul(lo*FRACUNIT, adj));
|
|
else
|
|
return InvAngle(FixedMul(lo*FRACUNIT, adj));
|
|
}
|
|
|
|
if (fan)
|
|
return InvAngle(ra);
|
|
else
|
|
return ra;
|
|
}
|
|
|
|
angle_t FixedAngleC(fixed_t fa, fixed_t factor)
|
|
{
|
|
angle_t wa = ANGLE_180;
|
|
fixed_t wf = 180*FRACUNIT;
|
|
angle_t ra = 0;
|
|
const fixed_t cfa = fa;
|
|
fixed_t cwf = wf;
|
|
|
|
if (fa == 0)
|
|
return 0;
|
|
|
|
// -2,147,483,648 has no absolute value in a 32 bit signed integer
|
|
// so this code _would_ infinite loop if passed it
|
|
if (fa == INT32_MIN)
|
|
return 0;
|
|
|
|
if (factor == 0)
|
|
return FixedAngle(fa);
|
|
else if (factor > 0)
|
|
cwf = wf = FixedMul(wf, factor);
|
|
else if (factor < 0)
|
|
cwf = wf = FixedDiv(wf, -factor);
|
|
|
|
fa = abs(fa);
|
|
|
|
while (fa)
|
|
{
|
|
while (fa < wf)
|
|
{
|
|
wa /= 2;
|
|
wf /= 2;
|
|
}
|
|
ra = ra + wa;
|
|
fa = fa - wf;
|
|
}
|
|
|
|
return AngleAdj(cfa, cwf, ra);
|
|
}
|
|
|
|
angle_t FixedAngle(fixed_t fa)
|
|
{
|
|
angle_t wa = ANGLE_180;
|
|
fixed_t wf = 180*FRACUNIT;
|
|
angle_t ra = 0;
|
|
const fixed_t cfa = fa;
|
|
const fixed_t cwf = wf;
|
|
|
|
if (fa == 0)
|
|
return 0;
|
|
|
|
// -2,147,483,648 has no absolute value in a 32 bit signed integer
|
|
// so this code _would_ infinite loop if passed it
|
|
if (fa == INT32_MIN)
|
|
return 0;
|
|
|
|
fa = abs(fa);
|
|
|
|
while (fa)
|
|
{
|
|
while (fa < wf)
|
|
{
|
|
wa /= 2;
|
|
wf /= 2;
|
|
}
|
|
ra = ra + wa;
|
|
fa = fa - wf;
|
|
}
|
|
|
|
return AngleAdj(cfa, cwf, ra);
|
|
}
|
|
|
|
|
|
#include "t_ftan.c"
|
|
|
|
#include "t_fsin.c"
|
|
fixed_t *finecosine = &finesine[FINEANGLES/4];
|
|
|
|
#include "t_tan2a.c"
|
|
|
|
#include "t_facon.c"
|
|
|
|
|
|
FUNCMATH angle_t FixedAcos(fixed_t x)
|
|
{
|
|
if (-FRACUNIT > x || x >= FRACUNIT) return 0;
|
|
return fineacon[((x<<(FINE_FRACBITS-FRACBITS)))+FRACUNIT];
|
|
}
|
|
|
|
//
|
|
// AngleBetweenVectors
|
|
//
|
|
// This checks to see if a point is inside the ranges of a polygon
|
|
//
|
|
angle_t FV2_AngleBetweenVectors(const vector2_t *Vector1, const vector2_t *Vector2)
|
|
{
|
|
// Remember, above we said that the Dot Product of returns the cosine of the angle
|
|
// between 2 vectors? Well, that is assuming they are unit vectors (normalize vectors).
|
|
// So, if we don't have a unit vector, then instead of just saying arcCos(DotProduct(A, B))
|
|
// We need to divide the dot product by the magnitude of the 2 vectors multiplied by each other.
|
|
// Here is the equation: arc cosine of (V . W / || V || * || W || )
|
|
// the || V || means the magnitude of V. This then cancels out the magnitudes dot product magnitudes.
|
|
// But basically, if you have normalize vectors already, you can forget about the magnitude part.
|
|
|
|
// Get the dot product of the vectors
|
|
fixed_t dotProduct = FV2_Dot(Vector1, Vector2);
|
|
|
|
// Get the product of both of the vectors magnitudes
|
|
fixed_t vectorsMagnitude = FixedMul(FV2_Magnitude(Vector1), FV2_Magnitude(Vector2));
|
|
|
|
// Return the arc cosine of the (dotProduct / vectorsMagnitude) which is the angle in RADIANS.
|
|
return FixedAcos(FixedDiv(dotProduct, vectorsMagnitude));
|
|
}
|
|
|
|
angle_t FV3_AngleBetweenVectors(const vector3_t *Vector1, const vector3_t *Vector2)
|
|
{
|
|
// Remember, above we said that the Dot Product of returns the cosine of the angle
|
|
// between 2 vectors? Well, that is assuming they are unit vectors (normalize vectors).
|
|
// So, if we don't have a unit vector, then instead of just saying arcCos(DotProduct(A, B))
|
|
// We need to divide the dot product by the magnitude of the 2 vectors multiplied by each other.
|
|
// Here is the equation: arc cosine of (V . W / || V || * || W || )
|
|
// the || V || means the magnitude of V. This then cancels out the magnitudes dot product magnitudes.
|
|
// But basically, if you have normalize vectors already, you can forget about the magnitude part.
|
|
|
|
// Get the dot product of the vectors
|
|
fixed_t dotProduct = FV3_Dot(Vector1, Vector2);
|
|
|
|
// Get the product of both of the vectors magnitudes
|
|
fixed_t vectorsMagnitude = FixedMul(FV3_Magnitude(Vector1), FV3_Magnitude(Vector2));
|
|
|
|
// Return the arc cosine of the (dotProduct / vectorsMagnitude) which is the angle in RADIANS.
|
|
return FixedAcos(FixedDiv(dotProduct, vectorsMagnitude));
|
|
}
|
|
|
|
//
|
|
// InsidePolygon
|
|
//
|
|
// This checks to see if a point is inside the ranges of a polygon
|
|
//
|
|
boolean FV2_InsidePolygon(const vector2_t *vIntersection, const vector2_t *Poly, const INT32 vertexCount)
|
|
{
|
|
INT32 i;
|
|
UINT64 Angle = 0; // Initialize the angle
|
|
vector2_t vA, vB; // Create temp vectors
|
|
|
|
// Just because we intersected the plane, doesn't mean we were anywhere near the polygon.
|
|
// This functions checks our intersection point to make sure it is inside of the polygon.
|
|
// This is another tough function to grasp at first, but let me try and explain.
|
|
// It's a brilliant method really, what it does is create triangles within the polygon
|
|
// from the intersection point. It then adds up the inner angle of each of those triangles.
|
|
// If the angles together add up to 360 degrees (or 2 * PI in radians) then we are inside!
|
|
// If the angle is under that value, we must be outside of polygon. To further
|
|
// understand why this works, take a pencil and draw a perfect triangle. Draw a dot in
|
|
// the middle of the triangle. Now, from that dot, draw a line to each of the vertices.
|
|
// Now, we have 3 triangles within that triangle right? Now, we know that if we add up
|
|
// all of the angles in a triangle we get 360 right? Well, that is kinda what we are doing,
|
|
// but the inverse of that. Say your triangle is an isosceles triangle, so add up the angles
|
|
// and you will get 360 degree angles. 90 + 90 + 90 is 360.
|
|
|
|
for (i = 0; i < vertexCount; i++) // Go in a circle to each vertex and get the angle between
|
|
{
|
|
FV2_Point2Vec(&Poly[i], vIntersection, &vA); // Subtract the intersection point from the current vertex
|
|
// Subtract the point from the next vertex
|
|
FV2_Point2Vec(&Poly[(i + 1) % vertexCount], vIntersection, &vB);
|
|
|
|
Angle += FV2_AngleBetweenVectors(&vA, &vB); // Find the angle between the 2 vectors and add them all up as we go along
|
|
}
|
|
|
|
// Now that we have the total angles added up, we need to check if they add up to 360 degrees.
|
|
// Since we are using the dot product, we are working in radians, so we check if the angles
|
|
// equals 2*PI. We defined PI in 3DMath.h. You will notice that we use a MATCH_FACTOR
|
|
// in conjunction with our desired degree. This is because of the inaccuracy when working
|
|
// with floating point numbers. It usually won't always be perfectly 2 * PI, so we need
|
|
// to use a little twiddling. I use .9999, but you can change this to fit your own desired accuracy.
|
|
|
|
if(Angle >= ANGLE_MAX) // If the angle is greater than 2 PI, (360 degrees)
|
|
return 1; // The point is inside of the polygon
|
|
|
|
return 0; // If you get here, it obviously wasn't inside the polygon.
|
|
}
|
|
|
|
boolean FV3_InsidePolygon(const vector3_t *vIntersection, const vector3_t *Poly, const INT32 vertexCount)
|
|
{
|
|
INT32 i;
|
|
UINT64 Angle = 0; // Initialize the angle
|
|
vector3_t vA, vB; // Create temp vectors
|
|
|
|
// Just because we intersected the plane, doesn't mean we were anywhere near the polygon.
|
|
// This functions checks our intersection point to make sure it is inside of the polygon.
|
|
// This is another tough function to grasp at first, but let me try and explain.
|
|
// It's a brilliant method really, what it does is create triangles within the polygon
|
|
// from the intersection point. It then adds up the inner angle of each of those triangles.
|
|
// If the angles together add up to 360 degrees (or 2 * PI in radians) then we are inside!
|
|
// If the angle is under that value, we must be outside of polygon. To further
|
|
// understand why this works, take a pencil and draw a perfect triangle. Draw a dot in
|
|
// the middle of the triangle. Now, from that dot, draw a line to each of the vertices.
|
|
// Now, we have 3 triangles within that triangle right? Now, we know that if we add up
|
|
// all of the angles in a triangle we get 360 right? Well, that is kinda what we are doing,
|
|
// but the inverse of that. Say your triangle is an isosceles triangle, so add up the angles
|
|
// and you will get 360 degree angles. 90 + 90 + 90 is 360.
|
|
|
|
for (i = 0; i < vertexCount; i++) // Go in a circle to each vertex and get the angle between
|
|
{
|
|
FV3_Point2Vec(&Poly[i], vIntersection, &vA); // Subtract the intersection point from the current vertex
|
|
// Subtract the point from the next vertex
|
|
FV3_Point2Vec(&Poly[(i + 1) % vertexCount], vIntersection, &vB);
|
|
|
|
Angle += FV3_AngleBetweenVectors(&vA, &vB); // Find the angle between the 2 vectors and add them all up as we go along
|
|
}
|
|
|
|
// Now that we have the total angles added up, we need to check if they add up to 360 degrees.
|
|
// Since we are using the dot product, we are working in radians, so we check if the angles
|
|
// equals 2*PI. We defined PI in 3DMath.h. You will notice that we use a MATCH_FACTOR
|
|
// in conjunction with our desired degree. This is because of the inaccuracy when working
|
|
// with floating point numbers. It usually won't always be perfectly 2 * PI, so we need
|
|
// to use a little twiddling. I use .9999, but you can change this to fit your own desired accuracy.
|
|
|
|
if(Angle >= ANGLE_MAX) // If the angle is greater than 2 PI, (360 degrees)
|
|
return 1; // The point is inside of the polygon
|
|
|
|
return 0; // If you get here, it obviously wasn't inside the polygon.
|
|
}
|
|
|
|
//
|
|
// IntersectedPolygon
|
|
//
|
|
// This checks if a line is intersecting a polygon
|
|
//
|
|
boolean FV3_IntersectedPolygon(const vector3_t *vPoly, const vector3_t *vLine, const INT32 vertexCount, vector3_t *collisionPoint)
|
|
{
|
|
vector3_t vNormal, vIntersection;
|
|
fixed_t originDistance = 0*FRACUNIT;
|
|
|
|
|
|
// First we check to see if our line intersected the plane. If this isn't true
|
|
// there is no need to go on, so return false immediately.
|
|
// We pass in address of vNormal and originDistance so we only calculate it once
|
|
|
|
if(!FV3_IntersectedPlane(vPoly, vLine, &vNormal, &originDistance))
|
|
return false;
|
|
|
|
// Now that we have our normal and distance passed back from IntersectedPlane(),
|
|
// we can use it to calculate the intersection point. The intersection point
|
|
// is the point that actually is ON the plane. It is between the line. We need
|
|
// this point test next, if we are inside the polygon. To get the I-Point, we
|
|
// give our function the normal of the plane, the points of the line, and the originDistance.
|
|
|
|
FV3_IntersectionPoint(&vNormal, vLine, originDistance, &vIntersection);
|
|
|
|
// Now that we have the intersection point, we need to test if it's inside the polygon.
|
|
// To do this, we pass in :
|
|
// (our intersection point, the polygon, and the number of vertices our polygon has)
|
|
|
|
if(FV3_InsidePolygon(&vIntersection, vPoly, vertexCount))
|
|
{
|
|
if (collisionPoint != NULL) // Optional - load the collision point.
|
|
{
|
|
collisionPoint->x = vIntersection.x;
|
|
collisionPoint->y = vIntersection.y;
|
|
collisionPoint->z = vIntersection.z;
|
|
}
|
|
return true; // We collided!
|
|
}
|
|
|
|
// If we get here, we must have NOT collided
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// RotateVector
|
|
//
|
|
// Rotates a vector around another vector
|
|
//
|
|
void FV3_Rotate(vector3_t *rotVec, const vector3_t *axisVec, const angle_t angle)
|
|
{
|
|
// Rotate the point (x,y,z) around the vector (u,v,w)
|
|
fixed_t ux = FixedMul(axisVec->x, rotVec->x);
|
|
fixed_t uy = FixedMul(axisVec->x, rotVec->y);
|
|
fixed_t uz = FixedMul(axisVec->x, rotVec->z);
|
|
fixed_t vx = FixedMul(axisVec->y, rotVec->x);
|
|
fixed_t vy = FixedMul(axisVec->y, rotVec->y);
|
|
fixed_t vz = FixedMul(axisVec->y, rotVec->z);
|
|
fixed_t wx = FixedMul(axisVec->z, rotVec->x);
|
|
fixed_t wy = FixedMul(axisVec->z, rotVec->y);
|
|
fixed_t wz = FixedMul(axisVec->z, rotVec->z);
|
|
fixed_t sa = FINESINE(angle);
|
|
fixed_t ca = FINECOSINE(angle);
|
|
fixed_t ua = ux+vy+wz;
|
|
fixed_t ax = FixedMul(axisVec->x,ua);
|
|
fixed_t ay = FixedMul(axisVec->y,ua);
|
|
fixed_t az = FixedMul(axisVec->z,ua);
|
|
fixed_t xs = FixedMul(axisVec->x,axisVec->x);
|
|
fixed_t ys = FixedMul(axisVec->y,axisVec->y);
|
|
fixed_t zs = FixedMul(axisVec->z,axisVec->z);
|
|
fixed_t bx = FixedMul(rotVec->x,ys+zs);
|
|
fixed_t by = FixedMul(rotVec->y,xs+zs);
|
|
fixed_t bz = FixedMul(rotVec->z,xs+ys);
|
|
fixed_t cx = FixedMul(axisVec->x,vy+wz);
|
|
fixed_t cy = FixedMul(axisVec->y,ux+wz);
|
|
fixed_t cz = FixedMul(axisVec->z,ux+vy);
|
|
fixed_t dx = FixedMul(bx-cx, ca);
|
|
fixed_t dy = FixedMul(by-cy, ca);
|
|
fixed_t dz = FixedMul(bz-cz, ca);
|
|
fixed_t ex = FixedMul(vz-wy, sa);
|
|
fixed_t ey = FixedMul(wx-uz, sa);
|
|
fixed_t ez = FixedMul(uy-vx, sa);
|
|
|
|
rotVec->x = ax+dx+ex;
|
|
rotVec->y = ay+dy+ey;
|
|
rotVec->z = az+dz+ez;
|
|
}
|
|
|
|
#define M(row,col) dest->m[row * 4 + col]
|
|
matrix_t *FM_Rotate(matrix_t *dest, angle_t angle, fixed_t x, fixed_t y, fixed_t z)
|
|
{
|
|
const fixed_t sinA = FINESINE(angle>>ANGLETOFINESHIFT);
|
|
const fixed_t cosA = FINECOSINE(angle>>ANGLETOFINESHIFT);
|
|
const fixed_t invCosA = FRACUNIT - cosA;
|
|
vector3_t nrm;
|
|
fixed_t xSq, ySq, zSq;
|
|
fixed_t sx, sy, sz;
|
|
fixed_t sxy, sxz, syz;
|
|
|
|
nrm.x = x;
|
|
nrm.y = y;
|
|
nrm.z = z;
|
|
FV3_Normalize(&nrm);
|
|
|
|
x = nrm.x;
|
|
y = nrm.y;
|
|
z = nrm.z;
|
|
|
|
xSq = FixedMul(x, FixedMul(invCosA,x));
|
|
ySq = FixedMul(y, FixedMul(invCosA,y));
|
|
zSq = FixedMul(z, FixedMul(invCosA,z));
|
|
|
|
sx = FixedMul(sinA, x);
|
|
sy = FixedMul(sinA, y);
|
|
sz = FixedMul(sinA, z);
|
|
|
|
sxy = FixedMul(x, FixedMul(invCosA,y));
|
|
sxz = FixedMul(x, FixedMul(invCosA,z));
|
|
syz = FixedMul(y, FixedMul(invCosA,z));
|
|
|
|
|
|
M(0, 0) = xSq + cosA;
|
|
M(1, 0) = sxy - sz;
|
|
M(2, 0) = sxz + sy;
|
|
M(3, 0) = 0;
|
|
|
|
M(0, 1) = sxy + sz;
|
|
M(1, 1) = ySq + cosA;
|
|
M(2, 1) = syz - sx;
|
|
M(3, 1) = 0;
|
|
|
|
M(0, 2) = sxz - sy;
|
|
M(1, 2) = syz + sx;
|
|
M(2, 2) = zSq + cosA;
|
|
M(3, 2) = 0;
|
|
|
|
M(0, 3) = 0;
|
|
M(1, 3) = 0;
|
|
M(2, 3) = 0;
|
|
M(3, 3) = FRACUNIT;
|
|
|
|
return dest;
|
|
}
|
|
|
|
|
|
matrix_t *FM_RotateX(matrix_t *dest, angle_t rad)
|
|
{
|
|
const angle_t fa = rad>>ANGLETOFINESHIFT;
|
|
const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
|
|
|
|
M(0, 0) = FRACUNIT;
|
|
M(0, 1) = 0;
|
|
M(0, 2) = 0;
|
|
M(0, 3) = 0;
|
|
M(1, 0) = 0;
|
|
M(1, 1) = cosrad;
|
|
M(1, 2) = sinrad;
|
|
M(1, 3) = 0;
|
|
M(2, 0) = 0;
|
|
M(2, 1) = -sinrad;
|
|
M(2, 2) = cosrad;
|
|
M(2, 3) = 0;
|
|
M(3, 0) = 0;
|
|
M(3, 1) = 0;
|
|
M(3, 2) = 0;
|
|
M(3, 3) = FRACUNIT;
|
|
|
|
return dest;
|
|
}
|
|
|
|
matrix_t *FM_RotateY(matrix_t *dest, angle_t rad)
|
|
{
|
|
const angle_t fa = rad>>ANGLETOFINESHIFT;
|
|
const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
|
|
|
|
M(0, 0) = cosrad;
|
|
M(0, 1) = 0;
|
|
M(0, 2) = -sinrad;
|
|
M(0, 3) = 0;
|
|
M(1, 0) = 0;
|
|
M(1, 1) = FRACUNIT;
|
|
M(1, 2) = 0;
|
|
M(1, 3) = 0;
|
|
M(2, 0) = sinrad;
|
|
M(2, 1) = 0;
|
|
M(2, 2) = cosrad;
|
|
M(2, 3) = 0;
|
|
M(3, 0) = 0;
|
|
M(3, 1) = 0;
|
|
M(3, 2) = 0;
|
|
M(3, 3) = FRACUNIT;
|
|
|
|
return dest;
|
|
}
|
|
|
|
matrix_t *FM_RotateZ(matrix_t *dest, angle_t rad)
|
|
{
|
|
const angle_t fa = rad>>ANGLETOFINESHIFT;
|
|
const fixed_t cosrad = FINECOSINE(fa), sinrad = FINESINE(fa);
|
|
|
|
M(0, 0) = cosrad;
|
|
M(0, 1) = sinrad;
|
|
M(0, 2) = 0;
|
|
M(0, 3) = 0;
|
|
M(1, 0) = -sinrad;
|
|
M(1, 1) = cosrad;
|
|
M(1, 2) = 0;
|
|
M(1, 3) = 0;
|
|
M(2, 0) = 0;
|
|
M(2, 1) = 0;
|
|
M(2, 2) = FRACUNIT;
|
|
M(2, 3) = 0;
|
|
M(3, 0) = 0;
|
|
M(3, 1) = 0;
|
|
M(3, 2) = 0;
|
|
M(3, 3) = FRACUNIT;
|
|
|
|
return dest;
|
|
}
|
|
|
|
#undef M
|