mirror of
https://github.com/zeldaret/oot.git
synced 2024-11-25 09:45:02 +00:00
Lightweight trigonometry doc (#1356)
* Doc units of trig functions * "Very simple" yet I made a mistake * sins returns in [-0x7FFF,0x7FFF] as the [-1,1] range * Also `sys_math_atan.c` * Remove `@param`s without descriptions * Add note on Math_Atan2S/F arguments being unlike atan2 * "from (1,0) to (x,y)" -> "from vector ..." * arg names -> `angle` * Improve `@return` comment on atans
This commit is contained in:
parent
0b38f6e678
commit
0283493db8
10 changed files with 104 additions and 40 deletions
|
@ -1945,7 +1945,7 @@ s8 PadUtils_GetRelX(Input* input);
|
||||||
s8 PadUtils_GetRelY(Input* input);
|
s8 PadUtils_GetRelY(Input* input);
|
||||||
void PadUtils_UpdateRelXY(Input* input);
|
void PadUtils_UpdateRelXY(Input* input);
|
||||||
s32 PadSetup_Init(OSMesgQueue* mq, u8* outMask, OSContStatus* status);
|
s32 PadSetup_Init(OSMesgQueue* mq, u8* outMask, OSContStatus* status);
|
||||||
f32 Math_FTanF(f32 x);
|
f32 Math_FTanF(f32 angle);
|
||||||
f32 Math_FFloorF(f32 x);
|
f32 Math_FFloorF(f32 x);
|
||||||
f32 Math_FCeilF(f32 x);
|
f32 Math_FCeilF(f32 x);
|
||||||
f32 Math_FRoundF(f32 x);
|
f32 Math_FRoundF(f32 x);
|
||||||
|
@ -2043,8 +2043,8 @@ s32 JpegDecoder_ParseNextSymbol(JpegHuffmanTable* hTable, s16* outCoeff, s8* out
|
||||||
u16 JpegDecoder_ReadBits(u8 len);
|
u16 JpegDecoder_ReadBits(u8 len);
|
||||||
s32 osPfsFreeBlocks(OSPfs* pfs, s32* leftoverBytes);
|
s32 osPfsFreeBlocks(OSPfs* pfs, s32* leftoverBytes);
|
||||||
void guScale(Mtx* m, f32 x, f32 y, f32 z);
|
void guScale(Mtx* m, f32 x, f32 y, f32 z);
|
||||||
f32 sinf(f32);
|
f32 sinf(f32 angle);
|
||||||
s16 sins(u16);
|
s16 sins(u16 angle);
|
||||||
OSTask* _VirtualToPhysicalTask(OSTask* intp);
|
OSTask* _VirtualToPhysicalTask(OSTask* intp);
|
||||||
void osSpTaskLoad(OSTask* intp);
|
void osSpTaskLoad(OSTask* intp);
|
||||||
void osSpTaskStartGo(OSTask* tp);
|
void osSpTaskStartGo(OSTask* tp);
|
||||||
|
@ -2108,8 +2108,8 @@ s32 osPfsDeleteFile(OSPfs* pfs, u16 companyCode, u32 gameCode, u8* gameName, u8*
|
||||||
s32 __osPfsReleasePages(OSPfs* pfs, __OSInode* inode, u8 initialPage, u8 bank, __OSInodeUnit* finalPage);
|
s32 __osPfsReleasePages(OSPfs* pfs, __OSInode* inode, u8 initialPage, u8 bank, __OSInodeUnit* finalPage);
|
||||||
void guOrthoF(f32[4][4], f32, f32, f32, f32, f32, f32, f32);
|
void guOrthoF(f32[4][4], f32, f32, f32, f32, f32, f32, f32);
|
||||||
void guOrtho(Mtx*, f32, f32, f32, f32, f32, f32, f32);
|
void guOrtho(Mtx*, f32, f32, f32, f32, f32, f32, f32);
|
||||||
f32 cosf(f32);
|
f32 cosf(f32 angle);
|
||||||
s16 coss(u16);
|
s16 coss(u16 angle);
|
||||||
void osViSetEvent(OSMesgQueue* mq, OSMesg msg, u32 retraceCount);
|
void osViSetEvent(OSMesgQueue* mq, OSMesg msg, u32 retraceCount);
|
||||||
s32 osPfsIsPlug(OSMesgQueue* mq, u8* pattern);
|
s32 osPfsIsPlug(OSMesgQueue* mq, u8* pattern);
|
||||||
void __osPfsRequestData(u8 cmd);
|
void __osPfsRequestData(u8 cmd);
|
||||||
|
|
|
@ -3,9 +3,13 @@
|
||||||
|
|
||||||
s32 gUseAtanContFrac;
|
s32 gUseAtanContFrac;
|
||||||
|
|
||||||
f32 Math_FTanF(f32 x) {
|
/**
|
||||||
f32 sin = sinf(x);
|
* @param angle radians
|
||||||
f32 cos = cosf(x);
|
* @return tan(angle)
|
||||||
|
*/
|
||||||
|
f32 Math_FTanF(f32 angle) {
|
||||||
|
f32 sin = sinf(angle);
|
||||||
|
f32 cos = cosf(angle);
|
||||||
|
|
||||||
return sin / cos;
|
return sin / cos;
|
||||||
}
|
}
|
||||||
|
@ -42,7 +46,7 @@ f32 Math_FAtanTaylorQF(f32 x) {
|
||||||
const f32* c = coeffs;
|
const f32* c = coeffs;
|
||||||
f32 term;
|
f32 term;
|
||||||
|
|
||||||
while (1) {
|
while (true) {
|
||||||
term = *c++ * exp;
|
term = *c++ * exp;
|
||||||
if (poly + term == poly) {
|
if (poly + term == poly) {
|
||||||
break;
|
break;
|
||||||
|
@ -124,6 +128,9 @@ f32 Math_FAtanContFracF(f32 x) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return arctan(x) in radians, in (-pi/2,pi/2) range
|
||||||
|
*/
|
||||||
f32 Math_FAtanF(f32 x) {
|
f32 Math_FAtanF(f32 x) {
|
||||||
if (!gUseAtanContFrac) {
|
if (!gUseAtanContFrac) {
|
||||||
return Math_FAtanTaylorF(x);
|
return Math_FAtanTaylorF(x);
|
||||||
|
@ -132,6 +139,9 @@ f32 Math_FAtanF(f32 x) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return angle to (x,y) from vector (1,0) around (0,0) in radians, in (-pi,pi] range
|
||||||
|
*/
|
||||||
f32 Math_FAtan2F(f32 y, f32 x) {
|
f32 Math_FAtan2F(f32 y, f32 x) {
|
||||||
if (x == 0.0f) {
|
if (x == 0.0f) {
|
||||||
if (y == 0.0f) {
|
if (y == 0.0f) {
|
||||||
|
@ -152,10 +162,16 @@ f32 Math_FAtan2F(f32 y, f32 x) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return arcsin(x) in radians, in [-pi/2,pi/2] range
|
||||||
|
*/
|
||||||
f32 Math_FAsinF(f32 x) {
|
f32 Math_FAsinF(f32 x) {
|
||||||
return Math_FAtan2F(x, sqrtf(1.0f - SQ(x)));
|
return Math_FAtan2F(x, sqrtf(1.0f - SQ(x)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return arccos(x) in radians, in [0,pi] range
|
||||||
|
*/
|
||||||
f32 Math_FAcosF(f32 x) {
|
f32 Math_FAcosF(f32 x) {
|
||||||
return M_PI / 2 - Math_FAsinF(x);
|
return M_PI / 2 - Math_FAsinF(x);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,10 +38,18 @@ f32 Math_PowF(f32 base, s32 exp) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param angle radians
|
||||||
|
* @return sin(angle)
|
||||||
|
*/
|
||||||
f32 Math_SinF(f32 angle) {
|
f32 Math_SinF(f32 angle) {
|
||||||
return sins((s16)(angle * (0x7FFF / M_PI))) * SHT_MINV;
|
return sins((s16)(angle * (0x7FFF / M_PI))) * SHT_MINV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param angle radians
|
||||||
|
* @return cos(angle)
|
||||||
|
*/
|
||||||
f32 Math_CosF(f32 angle) {
|
f32 Math_CosF(f32 angle) {
|
||||||
return coss((s16)(angle * (0x7FFF / M_PI))) * SHT_MINV;
|
return coss((s16)(angle * (0x7FFF / M_PI))) * SHT_MINV;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
static u16 sATan2Tbl[] = {
|
static u16 sAtan2Tbl[] = {
|
||||||
0x0000, 0x000A, 0x0014, 0x001F, 0x0029, 0x0033, 0x003D, 0x0047, 0x0051, 0x005C, 0x0066, 0x0070, 0x007A, 0x0084,
|
0x0000, 0x000A, 0x0014, 0x001F, 0x0029, 0x0033, 0x003D, 0x0047, 0x0051, 0x005C, 0x0066, 0x0070, 0x007A, 0x0084,
|
||||||
0x008F, 0x0099, 0x00A3, 0x00AD, 0x00B7, 0x00C2, 0x00CC, 0x00D6, 0x00E0, 0x00EA, 0x00F4, 0x00FF, 0x0109, 0x0113,
|
0x008F, 0x0099, 0x00A3, 0x00AD, 0x00B7, 0x00C2, 0x00CC, 0x00D6, 0x00E0, 0x00EA, 0x00F4, 0x00FF, 0x0109, 0x0113,
|
||||||
0x011D, 0x0127, 0x0131, 0x013C, 0x0146, 0x0150, 0x015A, 0x0164, 0x016F, 0x0179, 0x0183, 0x018D, 0x0197, 0x01A1,
|
0x011D, 0x0127, 0x0131, 0x013C, 0x0146, 0x0150, 0x015A, 0x0164, 0x016F, 0x0179, 0x0183, 0x018D, 0x0197, 0x01A1,
|
||||||
|
@ -77,24 +77,34 @@ static u16 sATan2Tbl[] = {
|
||||||
0x1FF6, 0x1FFB, 0x2000,
|
0x1FF6, 0x1FFB, 0x2000,
|
||||||
};
|
};
|
||||||
|
|
||||||
u16 Math_GetAtan2Tbl(f32 x, f32 y) {
|
/**
|
||||||
|
* @param y must be >= 0 and <= x
|
||||||
|
* @param x must be >= 0
|
||||||
|
* @return arctan(y/x) as binang, in [0,0x2000] range
|
||||||
|
*/
|
||||||
|
u16 Math_GetAtan2Tbl(f32 y, f32 x) {
|
||||||
u16 ret;
|
u16 ret;
|
||||||
|
|
||||||
if (y == 0.0f) {
|
if (x == 0.0f) {
|
||||||
ret = sATan2Tbl[0];
|
ret = sAtan2Tbl[0];
|
||||||
} else {
|
} else {
|
||||||
s32 tblIdx = ((x / y) * 1024.0f) + 0.5f;
|
s32 tblIdx = ((y / x) * 1024.0f) + 0.5f;
|
||||||
|
|
||||||
if (tblIdx >= ARRAY_COUNT(sATan2Tbl)) {
|
if (tblIdx >= ARRAY_COUNT(sAtan2Tbl)) {
|
||||||
ret = sATan2Tbl[0];
|
ret = sAtan2Tbl[0];
|
||||||
} else {
|
} else {
|
||||||
ret = sATan2Tbl[tblIdx];
|
ret = sAtan2Tbl[tblIdx];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return angle to (x,y) from vector (1,0) around (0,0) as binang, in [-0x8000,0x7FFF] range
|
||||||
|
*
|
||||||
|
* @note The arguments are (x,y), which is different from atan2's (y,x)
|
||||||
|
*/
|
||||||
s16 Math_Atan2S(f32 x, f32 y) {
|
s16 Math_Atan2S(f32 x, f32 y) {
|
||||||
s32 ret;
|
s32 ret;
|
||||||
|
|
||||||
|
@ -107,7 +117,7 @@ s16 Math_Atan2S(f32 x, f32 y) {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (-x < y) {
|
if (-x < y) {
|
||||||
ret = Math_GetAtan2Tbl(-x, y) + 0x4000;
|
ret = 0x4000 + Math_GetAtan2Tbl(-x, y);
|
||||||
} else {
|
} else {
|
||||||
ret = 0x8000 - Math_GetAtan2Tbl(y, -x);
|
ret = 0x8000 - Math_GetAtan2Tbl(y, -x);
|
||||||
}
|
}
|
||||||
|
@ -115,21 +125,27 @@ s16 Math_Atan2S(f32 x, f32 y) {
|
||||||
} else {
|
} else {
|
||||||
if (x < 0.0f) {
|
if (x < 0.0f) {
|
||||||
if (-y <= -x) {
|
if (-y <= -x) {
|
||||||
ret = Math_GetAtan2Tbl(-y, -x) + 0x8000;
|
ret = 0x8000 + Math_GetAtan2Tbl(-y, -x);
|
||||||
} else {
|
} else {
|
||||||
ret = 0xC000 - Math_GetAtan2Tbl(-x, -y);
|
ret = 0xC000 - Math_GetAtan2Tbl(-x, -y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (x < -y) {
|
if (x < -y) {
|
||||||
ret = Math_GetAtan2Tbl(x, -y) + 0xC000;
|
ret = 0xC000 + Math_GetAtan2Tbl(x, -y);
|
||||||
} else {
|
} else {
|
||||||
ret = -Math_GetAtan2Tbl(-y, x);
|
ret = -Math_GetAtan2Tbl(-y, x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
|
||||||
|
return (s16)ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return angle to (x,y) from vector (1,0) around (0,0) in radians, in [-pi,pi) range
|
||||||
|
*
|
||||||
|
* @note The arguments are (x,y), which is different from atan2's (y,x)
|
||||||
|
*/
|
||||||
f32 Math_Atan2F(f32 x, f32 y) {
|
f32 Math_Atan2F(f32 x, f32 y) {
|
||||||
return BINANG_TO_RAD(Math_Atan2S(x, y));
|
return BINANG_TO_RAD(Math_Atan2S(x, y));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,18 @@ void Lib_MemSet(u8* dest, size_t len, u8 val) {
|
||||||
// clang-format on
|
// clang-format on
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param angle binang
|
||||||
|
* @return cos(angle)
|
||||||
|
*/
|
||||||
f32 Math_CosS(s16 angle) {
|
f32 Math_CosS(s16 angle) {
|
||||||
return coss(angle) * SHT_MINV;
|
return coss(angle) * SHT_MINV;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param angle binang
|
||||||
|
* @return sin(angle)
|
||||||
|
*/
|
||||||
f32 Math_SinS(s16 angle) {
|
f32 Math_SinS(s16 angle) {
|
||||||
return sins(angle) * SHT_MINV;
|
return sins(angle) * SHT_MINV;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,11 @@ static const du pilo = { 0x3E6110B4, 0x611A6263 };
|
||||||
|
|
||||||
static const fu zero = { 0x00000000 };
|
static const fu zero = { 0x00000000 };
|
||||||
|
|
||||||
f32 cosf(f32 x) {
|
/**
|
||||||
|
* @param angle radians
|
||||||
|
* @return cos(angle)
|
||||||
|
*/
|
||||||
|
f32 cosf(f32 angle) {
|
||||||
f32 absx;
|
f32 absx;
|
||||||
f64 dx;
|
f64 dx;
|
||||||
f64 xSq;
|
f64 xSq;
|
||||||
|
@ -22,13 +26,13 @@ f32 cosf(f32 x) {
|
||||||
f64 dn;
|
f64 dn;
|
||||||
s32 n;
|
s32 n;
|
||||||
f64 result;
|
f64 result;
|
||||||
s32 ix = *(s32*)&x;
|
s32 ix = *(s32*)∠
|
||||||
s32 xpt = (ix >> 22);
|
s32 xpt = (ix >> 22);
|
||||||
|
|
||||||
xpt &= 0x1FF;
|
xpt &= 0x1FF;
|
||||||
|
|
||||||
if (xpt < 0x136) {
|
if (xpt < 0x136) {
|
||||||
absx = (x > 0) ? x : -x;
|
absx = (angle > 0) ? angle : -angle;
|
||||||
dx = absx;
|
dx = absx;
|
||||||
|
|
||||||
dn = dx * rpi.d + 0.5;
|
dn = dx * rpi.d + 0.5;
|
||||||
|
@ -51,7 +55,7 @@ f32 cosf(f32 x) {
|
||||||
}
|
}
|
||||||
return -(f32)result;
|
return -(f32)result;
|
||||||
}
|
}
|
||||||
if (x != x) {
|
if (angle != angle) {
|
||||||
return __libm_qnan_f;
|
return __libm_qnan_f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
#include "global.h"
|
#include "global.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param angle binang
|
||||||
|
* @return cos(angle)*0x7FFF
|
||||||
|
*/
|
||||||
s16 coss(u16 angle) {
|
s16 coss(u16 angle) {
|
||||||
return sins(angle + 0x4000);
|
return sins(angle + 0x4000);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,20 +14,24 @@ static const du pilo = { 0x3E6110B4, 0x611A6263 };
|
||||||
|
|
||||||
static const fu zero = { 0x00000000 };
|
static const fu zero = { 0x00000000 };
|
||||||
|
|
||||||
f32 sinf(f32 x) {
|
/**
|
||||||
|
* @param angle radians
|
||||||
|
* @return sin(angle)
|
||||||
|
*/
|
||||||
|
f32 sinf(f32 angle) {
|
||||||
f64 dx;
|
f64 dx;
|
||||||
f64 xSq;
|
f64 xSq;
|
||||||
f64 polyApprox;
|
f64 polyApprox;
|
||||||
f64 dn;
|
f64 dn;
|
||||||
s32 n;
|
s32 n;
|
||||||
f64 result;
|
f64 result;
|
||||||
s32 ix = *(s32*)&x;
|
s32 ix = *(s32*)∠
|
||||||
s32 xpt = (ix >> 22);
|
s32 xpt = (ix >> 22);
|
||||||
|
|
||||||
xpt &= 0x1FF;
|
xpt &= 0x1FF;
|
||||||
|
|
||||||
if (xpt < 0xFF) {
|
if (xpt < 0xFF) {
|
||||||
dx = x;
|
dx = angle;
|
||||||
|
|
||||||
if (xpt >= 0xE6) {
|
if (xpt >= 0xE6) {
|
||||||
xSq = SQ(dx);
|
xSq = SQ(dx);
|
||||||
|
@ -35,11 +39,11 @@ f32 sinf(f32 x) {
|
||||||
result = dx + (dx * xSq) * polyApprox;
|
result = dx + (dx * xSq) * polyApprox;
|
||||||
return (f32)result;
|
return (f32)result;
|
||||||
}
|
}
|
||||||
return x;
|
return angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (xpt < 0x136) {
|
if (xpt < 0x136) {
|
||||||
dx = x;
|
dx = angle;
|
||||||
dn = dx * rpi.d;
|
dn = dx * rpi.d;
|
||||||
n = ROUND(dn);
|
n = ROUND(dn);
|
||||||
dn = n;
|
dn = n;
|
||||||
|
@ -57,7 +61,7 @@ f32 sinf(f32 x) {
|
||||||
return -(f32)result;
|
return -(f32)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x != x) {
|
if (angle != angle) {
|
||||||
return __libm_qnan_f;
|
return __libm_qnan_f;
|
||||||
}
|
}
|
||||||
return zero.f;
|
return zero.f;
|
||||||
|
|
|
@ -2,18 +2,22 @@
|
||||||
|
|
||||||
#include "sintable.inc.c"
|
#include "sintable.inc.c"
|
||||||
|
|
||||||
s16 sins(u16 x) {
|
/**
|
||||||
|
* @param angle binang
|
||||||
|
* @return sin(angle)*0x7FFF
|
||||||
|
*/
|
||||||
|
s16 sins(u16 angle) {
|
||||||
s16 value;
|
s16 value;
|
||||||
|
|
||||||
x >>= 4;
|
angle >>= 4;
|
||||||
|
|
||||||
if (x & 0x400) {
|
if (angle & 0x400) {
|
||||||
value = sintable[0x3FF - (x & 0x3FF)];
|
value = sintable[0x3FF - (angle & 0x3FF)];
|
||||||
} else {
|
} else {
|
||||||
value = sintable[x & 0x3FF];
|
value = sintable[angle & 0x3FF];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x & 0x800) {
|
if (angle & 0x800) {
|
||||||
return -value;
|
return -value;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
return value;
|
||||||
|
|
|
@ -618,7 +618,7 @@ void BossGanon_IntroCutscene(BossGanon* this, PlayState* play) {
|
||||||
this->csCamEye.z = 300.0f;
|
this->csCamEye.z = 300.0f;
|
||||||
|
|
||||||
this->csCamAt.x = 0.0f;
|
this->csCamAt.x = 0.0f;
|
||||||
this->unk_704 = 1.2566371f;
|
this->unk_704 = 2 * M_PI / 5;
|
||||||
FALLTHROUGH;
|
FALLTHROUGH;
|
||||||
case 3:
|
case 3:
|
||||||
this->envLightMode = 0;
|
this->envLightMode = 0;
|
||||||
|
@ -3178,8 +3178,8 @@ void BossGanon_Update(Actor* thisx, PlayState* play2) {
|
||||||
this->unk_278.y = this->unk_2EC[0].y + 50.0f + 30.0f;
|
this->unk_278.y = this->unk_2EC[0].y + 50.0f + 30.0f;
|
||||||
this->unk_278.z = this->unk_2EC[0].z;
|
this->unk_278.z = this->unk_2EC[0].z;
|
||||||
|
|
||||||
xOffset = (sinf(i * 1.2566371f) * 600.0f);
|
xOffset = (sinf(i * (2 * M_PI / 5)) * 600.0f);
|
||||||
zOffset = (cosf(i * 1.2566371f) * 600.0f);
|
zOffset = (cosf(i * (2 * M_PI / 5)) * 600.0f);
|
||||||
|
|
||||||
// 5 or 6 light balls that go into the charge. not the same as the ones that he throws
|
// 5 or 6 light balls that go into the charge. not the same as the ones that he throws
|
||||||
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_BOSS_GANON, this->unk_1FC.x + xOffset,
|
Actor_SpawnAsChild(&play->actorCtx, &this->actor, play, ACTOR_BOSS_GANON, this->unk_1FC.x + xOffset,
|
||||||
|
|
Loading…
Reference in a new issue