From 953905ec6d570e6b7e41552f61666e5332b3b397 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Wed, 7 Oct 2026 12:08:40 +0300 Subject: [PATCH 1/9] Matched `nw4r::ef::DrawBillboardStrategy::DispParticle_Normal` --- .../drawstrategy/ef_drawbillboardstrategy.cpp | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp index 5830bc943..d3b02ed83 100644 --- a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp +++ b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp @@ -35,12 +35,12 @@ void DrawBillboardStrategy::Draw(const DrawInfo& rInfo, void DrawBillboardStrategy::DrawNormalBillboard(const DrawInfo& rInfo, ParticleManager* pManager) { - InitGraphics(rInfo, pManager); - const EmitterDrawSetting& rSetting = *pManager->mResource->GetEmitterDrawSetting(); - int flags = mNumTexmap > 0 ? 1 : 0; + InitGraphics(rInfo, pManager); + + bool flags = mNumTexmap > 0; //TODO: Kiwi, are we sure that flags is supposed to be an integer? const math::MTX34& rInfoMtx = *rInfo.GetViewMtx(); math::VEC2 pivot; @@ -167,7 +167,25 @@ inline void DrawBillboardStrategy::DispParticle_Normal( d1.y = vx_rs_cr_sx + vx_rs_sr_sy + vy_rc_sr_sx - vy_rc_cr_sy; d1.z = 0.0f; } else { - // todo + f32 exp0 = px - sx * px; + f32 exp1 = py - sy * py; + + p0.x = vx_rc * exp0 + vy_rs * exp1 + pos.x; + p0.y = vx_rs * exp0 - vy_rc * exp1 + pos.y; + p0.z = pos.z; + + f32 vx_rc_sx = vx_rc * sx; + f32 vy_rs_sy = vy_rs * sy; + f32 vx_rs_sx = vx_rs * sx; + f32 vy_rc_sy = vy_rc * sy; + + d0.x = vx_rc_sx - vy_rs_sy; + d0.y = vx_rs_sx + vy_rc_sy; + d0.z = 0.0f; + + d1.x = vx_rc_sx + vy_rs_sy; + d1.y = vx_rs_sx - vy_rc_sy; + d1.z = 0.0f; } DispPolygon(p0, d0, d1, flags); From bd179dacb33b31012360519f7411f7df08063634 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:47:42 +0300 Subject: [PATCH 2/9] Matched `nw4r::ef::DrawBillboardStrategy::DrawYBillboard` --- .../drawstrategy/ef_drawbillboardstrategy.cpp | 134 ++++++- src/nw4r/ef/ef_particlemanager.cpp | 335 +++++++++++++++++- 2 files changed, 451 insertions(+), 18 deletions(-) diff --git a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp index d3b02ed83..9cbdf80b3 100644 --- a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp +++ b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp @@ -192,11 +192,141 @@ inline void DrawBillboardStrategy::DispParticle_Normal( } void DrawBillboardStrategy::DrawYBillboard(const DrawInfo& rInfo, - ParticleManager* pManager); + ParticleManager* pManager) { + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + InitGraphics(rInfo, pManager); + + bool flags = mNumTexmap > 0; //TODO: Kiwi, are we sure that flags is supposed to be an integer? + const math::MTX34& rInfoMtx = *rInfo.GetViewMtx(); + + math::VEC2 pivot(rSetting.pivotX / 100.0f, rSetting.pivotY / 100.0f); + + math::MTX34 viewMtx; + pManager->CalcGlobalMtx(&viewMtx); + math::MTX34Mult(&viewMtx, &rInfoMtx, &viewMtx); + + if (rSetting.zOffset != 0.0f) { + CalcZOffset(&viewMtx, pManager, rInfo, rSetting.zOffset); + } + + f32 vx = math::FSqrt(viewMtx._00 * viewMtx._00 + viewMtx._10 * viewMtx._10 + + viewMtx._20 * viewMtx._20); + + f32 vy = math::FSqrt(viewMtx._01 * viewMtx._01 + viewMtx._11 * viewMtx._11 + + viewMtx._21 * viewMtx._21); + + f32 vz = 0.0f; + + f32 rc, rs; + f32 mag; + + mag = + math::FSqrt(rInfoMtx._11 * rInfoMtx._11 + rInfoMtx._21 * rInfoMtx._21); + if (mag == 0.0f) { + rs = 1.0f; + rc = 0.0f; + } else { + f32 denom = 1.0f / mag; + rs = rInfoMtx._21 * denom; + rc = rInfoMtx._11 * denom; + } + + GetFirstDrawParticleFunc pGetFirstFunc = GetGetFirstDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + GetNextDrawParticleFunc pGetNextFunc = GetGetNextDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + bool first = true; + + for (Particle* pIt = pGetFirstFunc(pManager); pIt != NULL; + pIt = pGetNextFunc(pManager, pIt)) { + + if (pIt->GetLifeStatus() != ReferencedObject::NW4R_EF_LS_ACTIVE) { + continue; + } + + f32 sx = pIt->Draw_GetSizeX(); + if (sx < std::numeric_limits::epsilon()) { + continue; + } + + f32 sy = pIt->Draw_GetSizeY(); + if (sy < std::numeric_limits::epsilon()) { + continue; + } + + SetupGP(pIt, rSetting, rInfo, first, false); + first = false; + + DispParticle_YBillboard(pIt, viewMtx, vx, vy, vz, rc, rs, sx, sy, pivot, + flags); + } +} inline void DrawBillboardStrategy::DispParticle_YBillboard( Particle* pParticle, const math::MTX34& rViewMtx, f32 vx, f32 vy, f32 vz, - f32 rc, f32 rs, f32 sx, f32 sy, const math::VEC2& rPivot, int flags); + f32 rc, f32 rs, f32 sx, f32 sy, const math::VEC2& rPivot, int flags) { + +#pragma unused(vz) + + math::VEC3 rot; + pParticle->Draw_GetRotate(&rot); + + math::VEC3 pos; + math::VEC3Transform(&pos, &rViewMtx, &pParticle->mParameter.mPosition); + + math::VEC3 p0; + math::VEC3 d0; + math::VEC3 d1; + + f32 vy_rc = vy * rc; + f32 vy_rs = vy * rs; + + f32 px = rPivot.x; + f32 py = rPivot.y; + + if (rot.z != 0.0f) { + f32 sr, cr; + math::SinCosRad(&sr, &cr, -rot.z); + + f32 cr_sx = cr * sx; + f32 sr_sx = sr * sx; + f32 cr_sy = cr * sy; + f32 sr_sy = sr * sy; + + f32 exp0 = -py - sr_sx * px + cr_sy * py; + + p0.x = pos.x + vx * (px - cr_sx * px - sr_sy * py); + p0.y = pos.y + vy_rc * exp0; + p0.z = pos.z + vy_rs * exp0; + + d0.x = vx * (cr_sx - sr_sy); + d0.y = vy_rc * (sr_sx + cr_sy); + d0.z = vy_rs * (sr_sx + cr_sy); + + d1.x = vx * (cr_sx + sr_sy); + d1.y = vy_rc * (sr_sx - cr_sy); + d1.z = vy_rs * (sr_sx - cr_sy); + + } else { + p0.x = pos.x + vx * (px - px * sx); + p0.y = pos.y + vy_rc * (py * sy - py); + p0.z = pos.z + vy_rs * (py * sy - py); + + d0.x = vx * sx; + d0.y = vy_rc * sy; + d0.z = vy_rs * sy; + + d1.x = vx * sx; + d1.y = -vy_rc * sy; + d1.z = -vy_rs * sy; + } + + DispPolygon(p0, d0, d1, flags); +} void DrawBillboardStrategy::DrawDirectionalBillboard(const DrawInfo& rInfo, ParticleManager* pManager); diff --git a/src/nw4r/ef/ef_particlemanager.cpp b/src/nw4r/ef/ef_particlemanager.cpp index f8935bc89..9b0528dad 100644 --- a/src/nw4r/ef/ef_particlemanager.cpp +++ b/src/nw4r/ef/ef_particlemanager.cpp @@ -143,10 +143,10 @@ void ParticleManager::Calc() { math::MTX34 mtxEmToGlb; mManagerEM->CalcGlobalMtx(&mtxEmToGlb); - math::MTX34 mtxEmToLoc; + math::MTX34 mtxEmToLoc;//0x2f0? math::MTX34Mult(&mtxEmToLoc, &mtxGlbToLoc, &mtxEmToGlb); - math::MTX34 mtxLocToEm; + math::MTX34 mtxLocToEm;//0x2c0? math::MTX34Inv(&mtxLocToEm, &mtxEmToLoc); math::MTX34 mtxGlbToLocNoTrans = mtxGlbToLoc; @@ -187,8 +187,8 @@ void ParticleManager::Calc() { mManagerEM->mManagerEF->SetFlagExistCalcRemain(true); } - math::VEC3 prevPos = pIt->mParameter.mPosition; - math::VEC3 prevVel = pIt->mParameter.mVelocity; + math::VEC3 prevPos = pIt->mParameter.mPosition;//0x138 + math::VEC3 prevVel = pIt->mParameter.mVelocity;//0x12c math::VEC3 prevDir; pIt->GetMoveDir(&prevDir); @@ -402,32 +402,332 @@ void ParticleManager::Calc() { } case AC_TARGET_UNK32: { - // 80019ec0 + /*f32 f1;rangeTable; // at 0x10 + u32 randomTable; // at 0x14 + u32 nameTable; + pTrackAsHeader->keyTable + +pTrackAsHeader->rangeTable+randomTable+nameTable + */ + u8 *r16 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + f32 stack_0x10 = *reinterpret_cast(r16 + 0x20 + 0x0); + AnimCurveExecuteF32(pPtclTrack, &stack_0x10, tick, seed, life); + math::VEC3 stack_0xf0;// = *reinterpret_cast(r16 + 0x20 + 0x4) * f1; + stack_0xf0.x = *reinterpret_cast(r16 + 0x20 + 0x4) * stack_0x10; + stack_0xf0.y = *reinterpret_cast(r16 + 0x20 + 0x8) * stack_0x10; + stack_0xf0.z = *reinterpret_cast(r16 + 0x20 + 0xC) * stack_0x10; + if (*reinterpret_cast(r16 + 0x20 + 0x10)) math::VEC3Transform(&stack_0xf0,&mtxLocToGlb,&stack_0xf0); + else math::VEC3Transform(&stack_0xf0,&mtxGlbToLoc,&stack_0xf0); + switch((r16 + 0x20)[0x11]) { + case 0: { + addVel += stack_0xf0; + break; + } + case 1: { + addPos += stack_0xf0; + break; + } + } break; } - case AC_TARGET_UNK34: { - // 8001a51c + case AC_TARGET_UNK39: { + // 80019fac + // r5:tick, r11:pTrackAsHeader->processFlag&AnimCurveHeader::PROC_FLAG_TIMING + u8 *r28 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + Random stack_0x8; + *((u32 *)&stack_0x8) = pIt->mRandSeed * 0x3F81F635 + + pTrackAsHeader->randomSeed * 0x30A74193 + + tick * 0x371097E7 + /*0x50000 - 0x40AD*/0x4BF53; + ((u8*)&stack_0x8)[2] ^= ((u8*)&stack_0x8)[3]; + ((u8*)&stack_0x8)[1] ^= ((u8*)&stack_0x8)[2]; + ((u8*)&stack_0x8)[0] ^= ((u8*)&stack_0x8)[1]; + if (pTrackAsHeader->processFlag&AnimCurveHeader::PROC_FLAG_TIMING) { + if (tick == 0 || tick % (*reinterpret_cast(r28 + 0x20 + 0x8) + 1) != 0) { + break; + } + } else { + if (tick != 0 && tick % (*reinterpret_cast(r28 + 0x20 + 0x8) + 1) != 0) { + break; + } + } + f32 stack_0xc = *reinterpret_cast(r28 + 0x20 + 0x0); + AnimCurveExecuteF32(pPtclTrack, &stack_0xc, tick, seed, life); + math::VEC3 stack_0xd8; + math::VEC3 stack_0xe4; + math::MTX34 stack_0x200; + if (*reinterpret_cast(r28 + 0x20 + 0xA) & 2 /* magic number */) { + f32 f2 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; + stack_0xe4.x = f2 * stack_0xc; + f32 f1 = stack_0x8.RandFloat(); f1 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; + stack_0xe4.y = f1 * stack_0xc; + f32 f0 = stack_0x8.RandFloat(); f0 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; + stack_0xe4.z = f0 * stack_0xc; + } else { + if (pIt->mTick == 0) { + stack_0xd8 = affect/*?*/; + } else { + stack_0xd8 = addVel/*?*/; + } + math::VEC3Transform(&stack_0xd8,&mtxLocToGlb,&stack_0xd8); + if (math::VEC3LenSq(&stack_0xd8) < 0x8000) { + stack_0xd8.y = 1.0f; + } else { + math::VEC3Normalize(&stack_0xd8, &stack_0xd8); + } + GetDirMtxY(&stack_0x200, stack_0xd8); + if (0.0f != *reinterpret_cast(r28 + 0x20 + 0x4)) { + f32 f1 = static_cast((u16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; + f32 f0 = stack_0x8.RandFloat(); + //f27:PI,f28:2.0f + f32 yrot = f1 * *reinterpret_cast(r28 + 0x20 + 0x4); + f32 xrot = 2.0f * (M_PI * f0); + stack_0xe4.x = std::sinf(yrot) * std::sinf(xrot); + stack_0xe4.y = std::cosf(yrot); + stack_0xe4.z = std::sinf(yrot) * std::cosf(xrot); + math::VEC3Scale(&stack_0xe4, &stack_0xe4, stack_0x8.RandFloat() * stack_0xc); + math::VEC3Transform(&stack_0xe4,&stack_0x200,&stack_0xe4); + } else { + stack_0xe4.x = 0.0f; + stack_0xe4.y = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) * stack_0xc; + stack_0xe4.z = 0.0f; + math::VEC3Transform(&stack_0xe4,&stack_0x200,&stack_0xe4); + } + } + math::VEC3Transform(&stack_0xe4,&mtxLocToGlb,&stack_0xe4); + switch((r28 + 0x20)[0xB]) { + case 0: { + addVel += stack_0xe4; + break; + } + case 1: { + addPos += stack_0xe4; + break; + } + } break; } - case AC_TARGET_UNK35: { - // 8001a6f0 - break; + case AC_TARGET_UNK38: { + // 8001a364 + struct UNKSTRUCT_UNK38 { + f32 FLOAT_0x0; + math::VEC3 VECTOR_0x4; + u8 BYTE_0x10; + u8 BYTE_0x11; + u8 BYTE_0x12; + u8 BYTE_0x13; + } stack_0x18c; + u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + stack_0x18c = *(UNKSTRUCT_UNK38*)(r8 + 0x20); + AnimCurveExecuteF32(pPtclTrack, &stack_0x18c.FLOAT_0x0, tick, seed, life); + math::VEC3 stack_0xcc; + math::VEC3 stack_0xc0; + math::VEC3 stack_0xb4; + math::VEC3 stack_0xa8; + Rotation2VecY(stack_0x18c.VECTOR_0x4, &stack_0xcc); + math::MTX34 stack_0x1d0; + math::MTX34RotAxisRad(&stack_0x1d0, &stack_0xcc, stack_0x18c.FLOAT_0x0); + if (stack_0x18c.BYTE_0x10) { + math::VEC3Transform(&stack_0xc0,&mtxGlbToLoc,&prevPos); + } else { + math::VEC3Transform(&stack_0xc0,&mtxLocToGlb,&prevPos); + stack_0xb4.x = 0.0f; + stack_0xb4.y = 0.0f; + stack_0xb4.z = 0.0f; + math::VEC3Transform(&stack_0xb4,&mtxLocToGlb,&stack_0xb4); + math::VEC3Sub(&stack_0xc0,&stack_0xc0,&stack_0xb4); + } + math::VEC3Transform(&stack_0xa8,&stack_0x1d0,&stack_0xc0); + math::VEC3Sub(&stack_0xa8,&stack_0xa8,&stack_0xc0); + if (stack_0x18c.BYTE_0x10) { + math::VEC3Transform(&stack_0xa8,&mtxGlbToLoc,&stack_0xa8); + } else { + math::VEC3Transform(&stack_0xa8,&mtxLocToGlb,&stack_0xa8); + } + switch (stack_0x18c.BYTE_0x11) { + case 0: { + addVel += stack_0xa8; + break; + } + case 1: { + addPos += stack_0xa8; + break; + } + } + break; } - case AC_TARGET_UNK36: { - // 8001a904 + case AC_TARGET_UNK34: { + // 8001a51c + struct UNKSTRUCT_UNK34 { + f32 FLOAT_0x0; + math::VEC3 VECTOR_0x4; + u8 BYTE_0x10; + u8 BYTE_0x11; + u8 BYTE_0x12; + u8 BYTE_0x13; + } stack_0x178; + u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + stack_0x178 = *(UNKSTRUCT_UNK34*)(r8 + 0x20); + AnimCurveExecuteF32(pPtclTrack, &stack_0x178.FLOAT_0x0, tick, seed, life); + math::VEC3 stack_0x9c; + math::VEC3 stack_0x90; + math::VEC3 stack_0x84; + if (stack_0x178.BYTE_0x10) { + math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); + } else { + math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); + stack_0x90.x = 0.0f; + stack_0x90.y = 0.0f; + stack_0x90.z = 0.0f; + math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); + math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); + } + math::VEC3Sub(&stack_0x84,&stack_0x178.VECTOR_0x4,&stack_0x9c); + if (0.0f != stack_0x84.x || 0.0f != stack_0x84.y || 0.0f != stack_0x84.z) { + math::VEC3Normalize(&stack_0x84,&stack_0x84); + } + stack_0x84 *= stack_0x178.FLOAT_0x0; + if (stack_0x178.BYTE_0x10) { + math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); + } else { + math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); + } + switch (stack_0x178.BYTE_0x11) { + case 0: { + addVel += stack_0x84; + break; + } + case 1: { + addPos += stack_0x84; + break; + } + } break; } - case AC_TARGET_UNK38: { - // 8001a364 + case AC_TARGET_UNK35: {//Newton? + // 8001a6f0 + struct UNKSTRUCT_UNK35 { + f32 FLOAT_0x0; + f32 FLOAT_0x4; + math::VEC3 VECTOR_0x8; + u8 BYTE_0x14; + u8 BYTE_0x15; + u8 BYTE_0x16; + u8 BYTE_0x17; + } infoCopy; + u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + infoCopy = *(UNKSTRUCT_UNK35*)(r8 + 0x20); + AnimCurveExecuteF32(pPtclTrack, &infoCopy.FLOAT_0x0, tick, seed, life); + /**/ + math::VEC3 stack_0x9c; + math::VEC3 stack_0x90; + math::VEC3 stack_0x84; + if (infoCopy.BYTE_0x14) { + math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); + } else { + math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); + stack_0x90.x = 0.0f; + stack_0x90.y = 0.0f; + stack_0x90.z = 0.0f; + math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); + math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); + } + math::VEC3Sub(&stack_0x84,&infoCopy.VECTOR_0x8,&stack_0x9c); + f32 f21 = math::VEC3LenSq(&stack_0x84); + if (0.0f != stack_0x84.x || 0.0f != stack_0x84.y || 0.0f != stack_0x84.z) { + math::VEC3Normalize(&stack_0x84,&stack_0x84); + } + stack_0x84 *= infoCopy.FLOAT_0x0; + f32 f0 = infoCopy.FLOAT_0x4*infoCopy.FLOAT_0x4; + if (f21 > f0) { + f0 /= f21; + stack_0x84 *= f0; + } + if (infoCopy.BYTE_0x14) { + math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); + } else { + math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); + } + switch (infoCopy.BYTE_0x15) { + case 0: { + addVel += stack_0x84; + break; + } + case 1: { + addPos += stack_0x84; + break; + } + } + /**/ break; } - case AC_TARGET_UNK39: { - // 80019fac + case AC_TARGET_UNK36: {//Vortex? + // 8001a904 + struct UNKSTRUCT_UNK36 { + f32 FLOAT_0x0; + f32 FLOAT_0x4; + f32 FLOAT_0x8; + math::VEC3 VECTOR_0x8; + u8 BYTE_0x14; + u8 BYTE_0x15; + u8 BYTE_0x16; + u8 BYTE_0x17; + } infoCopy; + u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); + infoCopy = *(UNKSTRUCT_UNK36*)(r8 + 0x20); + AnimCurveExecuteF32(pPtclTrack, &infoCopy.FLOAT_0x0, tick, seed, life); + /**/ + math::VEC3 stack_0x54; + math::VEC3 stack_0x9c; + math::VEC3 stack_0x90; + math::VEC3 stack_0x84; + Rotation2VecY(infoCopy.VECTOR_0x8, &stack_0x54); + if (infoCopy.BYTE_0x14) { + math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); + } else { + math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); + stack_0x90.x = 0.0f; + stack_0x90.y = 0.0f; + stack_0x90.z = 0.0f; + math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); + math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); + } + stack_0x9c *= math::VEC3Dot(&stack_0x9c,&stack_0x90); + infoCopy.FLOAT_0x8 *= infoCopy.FLOAT_0x8; + math::VEC3Sub(&stack_0x84,&infoCopy.VECTOR_0x8,&stack_0x9c); + f32 f20; + f32 f1 = math::VEC3Dot(&stack_0x84,&stack_0x84); + if (0.0f == f1) { + break; + } + if (f1 >= infoCopy.FLOAT_0x8) { + f20 = infoCopy.FLOAT_0x4; + } else { + f32 f3 = f1 / infoCopy.FLOAT_0x8; + f20 = (1.0f/*?*/ - f3) * infoCopy.FLOAT_0x4 + f3 * infoCopy.FLOAT_0x0; + } + math::VEC3Normalize(&stack_0x84,&stack_0x84); + math::VEC3Cross(&stack_0x84,&stack_0x84,&stack_0x54); + stack_0x84 *= f20; + if (infoCopy.BYTE_0x14) { + math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); + } else { + math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); + } + switch (infoCopy.BYTE_0x15) { + case 0: { + addVel += stack_0x84; + break; + } + case 1: { + addPos += stack_0x84; + break; + } + } + /**/ break; } } @@ -463,6 +763,9 @@ void ParticleManager::Calc() { } pIt->mTick++; + pIt->mParameter.mVelocity += addVel; + pIt->AddPosition(&addPos); + pIt->AddPosition(&pIt->mParameter.mVelocity); } mLastCalced = From 423e8046f72eaa120e14d12bbbd1b51379d04d3f Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 9 Oct 2026 16:05:42 +0300 Subject: [PATCH 3/9] Matched `nw4r::ef::DrawBillboardStrategy::DrawDirectionalBillboard` --- .../drawstrategy/ef_drawbillboardstrategy.h | 2 +- .../drawstrategy/ef_drawbillboardstrategy.cpp | 129 +++++++++++++++++- 2 files changed, 128 insertions(+), 3 deletions(-) diff --git a/include/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.h b/include/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.h index e85d1d992..239951ea3 100644 --- a/include/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.h +++ b/include/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.h @@ -41,7 +41,7 @@ class DrawBillboardStrategy : public DrawStrategyImpl { inline void DispParticle_Directional(Particle* pParticle, const math::MTX34& rViewMtx, f32 vx, f32 vy, f32 vz, f32 rc, f32 rs, f32 sx, - f32 sy, const math::VEC2& rPivot, + f32 sy, f32 vs, const math::VEC2& rPivot, int flags); void DispPolygon(const math::VEC3& rP, const math::VEC3& rD1, diff --git a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp index 9cbdf80b3..3244b27e2 100644 --- a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp +++ b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp @@ -329,11 +329,136 @@ inline void DrawBillboardStrategy::DispParticle_YBillboard( } void DrawBillboardStrategy::DrawDirectionalBillboard(const DrawInfo& rInfo, - ParticleManager* pManager); + ParticleManager* pManager) { + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + InitGraphics(rInfo, pManager); + + int flags = mNumTexmap > 0; + const math::MTX34& rInfoMtx = *rInfo.GetViewMtx(); + + math::VEC2 pivot; + pivot.x = rSetting.pivotX / 100.0f; + pivot.y = rSetting.pivotY / 100.0f; + + math::MTX34 viewMtx; + pManager->CalcGlobalMtx(&viewMtx); + math::MTX34Mult(&viewMtx, &rInfoMtx, &viewMtx); + + if (rSetting.zOffset != 0.0f) { + CalcZOffset(&viewMtx, pManager, rInfo, rSetting.zOffset); + } + + f32 vx = math::FSqrt(viewMtx._00 * viewMtx._00 + viewMtx._10 * viewMtx._10 + + viewMtx._20 * viewMtx._20); + + f32 vy = math::FSqrt(viewMtx._01 * viewMtx._01 + viewMtx._11 * viewMtx._11 + + viewMtx._21 * viewMtx._21); + + f32 vz = 0.0f; + AheadContext aheadContext(rInfoMtx, pManager); + CalcAheadFunc pCalcAheadFunc = GetCalcAheadFunc(pManager); + + GetFirstDrawParticleFunc pGetFirstFunc = GetGetFirstDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + GetNextDrawParticleFunc pGetNextFunc = GetGetNextDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + bool first = true; + + for (Particle* pIt = pGetFirstFunc(pManager); pIt != NULL; + pIt = pGetNextFunc(pManager, pIt)) { + + if (pIt->GetLifeStatus() != ReferencedObject::NW4R_EF_LS_ACTIVE) { + continue; + } + + f32 sx = pIt->Draw_GetSizeX(); + if (sx < std::numeric_limits::epsilon()) { + continue; + } + + f32 sy = pIt->Draw_GetSizeY(); + if (sy < std::numeric_limits::epsilon()) { + continue; + } + + SetupGP(pIt, rSetting, rInfo, first, false); + first = false; + + math::VEC3 yAxis; + + pCalcAheadFunc(&yAxis, &aheadContext, pIt); + math::VEC3TransformNormal(&yAxis, &viewMtx, &yAxis); + + f32 rc, rs; + f32 mag; + + mag = + math::FSqrt(yAxis.x * yAxis.x + yAxis.y * yAxis.y); + if (mag == 0.0f) { + rs = 0.0f; + rc = 1.0f; + } else { + f32 denom = 1.0f / mag; + rs = -yAxis.x * denom; + rc = yAxis.y * denom; + } + + f32 vs = 1.0f; + if (rSetting.typeOption0) { + math::VEC3 v; + pIt->GetMoveDir(&v); + + vs += 0.5f * math::FSqrt(math::VEC3Dot(&v, &v)) / sy; + } + + DispParticle_Directional(pIt, viewMtx, vx, vy, vz, rc, rs, sx, sy, vs, pivot, + flags); + } +} inline void DrawBillboardStrategy::DispParticle_Directional( Particle* pParticle, const math::MTX34& rViewMtx, f32 vx, f32 vy, f32 vz, - f32 rc, f32 rs, f32 sx, f32 sy, const math::VEC2& rPivot, int flags); + f32 rc, f32 rs, f32 sx, f32 sy, f32 vs, const math::VEC2& rPivot, int flags) { + +#pragma unused(vz) + + math::VEC3 pos; + math::VEC3Transform(&pos, &rViewMtx, &pParticle->mParameter.mPosition); + + f32 px = rPivot.x; + f32 py = rPivot.y; + + f32 vx_rc = vx * rc; + f32 vx_rs = vx * rs; + f32 vy_rc = vy * rc; + f32 vy_rs = vy * rs; + + f32 exp0 = px - sx * px; + f32 exp1 = sy * (vs + py - 1.0f) - py; + + math::VEC3 p0(vx_rc * exp0 + vy_rs * exp1 + pos.x, + vx_rs * exp0 - vy_rc * exp1 + pos.y, + pos.z); + + f32 vx_rc_sx = vx_rc * sx; + f32 vx_rs_sx = vx_rs * sx; + f32 vy_rc_sy_vs = vy_rc * sy * vs; + f32 vy_rs_sy_vs = vy_rs * sy * vs; + + math::VEC3 d0(vx_rc_sx - vy_rs_sy_vs, + vx_rs_sx + vy_rc_sy_vs, + 0.0f); + + math::VEC3 d1(vx_rc_sx + vy_rs_sy_vs, + vx_rs_sx - vy_rc_sy_vs, + 0.0f); + + DispPolygon(p0, d0, d1, flags); +} void DrawBillboardStrategy::DispPolygon(const math::VEC3& rP, const math::VEC3& rD1, From 6828281140f1950fd8f57bf97e24d497123fb008 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 9 Oct 2026 17:03:30 +0300 Subject: [PATCH 4/9] Linked `ef_drawbillboardstrategy.cpp` --- config/RSPE01_01/symbols.txt | 2 +- configure.py | 2 +- src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/RSPE01_01/symbols.txt b/config/RSPE01_01/symbols.txt index 4ea2385f4..1aa9dadc1 100644 --- a/config/RSPE01_01/symbols.txt +++ b/config/RSPE01_01/symbols.txt @@ -20101,7 +20101,7 @@ mFormatBufferSize__Q34nw4r2ut17TextWriterBase = .sdata:0x804BD388; // type:ob NW4R_EF_Version___29@unnamed@ef_effectsystem_cpp@ = .sdata:0x804BD390; // type:object size:0x4 scope:local align:4 data:4byte mDisplayVersion__Q34nw4r2ef12EffectSystem = .sdata:0x804BD394; // type:object size:0x1 scope:global align:1 data:byte billboard_tex0_u8__Q24nw4r2ef = .sdata:0x804BD3A0; // type:object size:0x20 scope:global align:32 -directional_tex0_u8__Q24nw4r2ef = .sdata:0x804BD3C0; // type:object size:0x20 scope:global +directional_tex0_u8__Q24nw4r2ef = .sdata:0x804BD3C0; // type:object size:0x20 scope:global align:32 free_tex0_u8__Q24nw4r2ef = .sdata:0x804BD3E0; // type:object size:0x8 scope:global @4676 = .sdata:0x804BD3E8; // type:object size:0x4 scope:local align:4 data:float @4668 = .sdata:0x804BD3F0; // type:object size:0x4 scope:local align:4 data:float diff --git a/configure.py b/configure.py index 4660adf5f..687dc7f2c 100755 --- a/configure.py +++ b/configure.py @@ -518,7 +518,7 @@ def MatchingFor(*versions): Object(Matching, "nw4r/ef/emform/ef_cube.cpp"), Object(Matching, "nw4r/ef/drawstrategy/ef_drawstrategybuilder.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/drawstrategy/ef_drawstrategyimpl.cpp"), - Object(NonMatching, "nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp"), + Object(Matching, "nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp"), Object(NonMatching, "nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp"), Object(NonMatching, "nw4r/ef/drawstrategy/ef_drawfreestrategy.cpp"), Object(Matching, "nw4r/ef/drawstrategy/ef_drawlinestrategy.cpp"), diff --git a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp index 3244b27e2..2cea8ac5c 100644 --- a/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp +++ b/src/nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp @@ -3,8 +3,8 @@ namespace nw4r { namespace ef { -static u8 billboard_tex0_u8[] = {0x00, 0x01, 0x00, 0x00, - 0x01, 0x00, 0x01, 0x01}; +static u8 billboard_tex0_u8[] ALIGN(32) = {0x00, 0x01, 0x00, 0x00, + 0x01, 0x00, 0x01, 0x01}; DrawBillboardStrategy::DrawBillboardStrategy() {} From f8c271e506fc8bd49763b9b17db1fa3fe4906993 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Sun, 11 Oct 2026 15:47:09 +0300 Subject: [PATCH 5/9] Made some progress on `ef_drawdirectionalstrategy.cpp` --- .../drawstrategy/ef_drawdirectionalstrategy.h | 14 + .../ef_drawdirectionalstrategy.cpp | 520 ++++++++++++++++++ 2 files changed, 534 insertions(+) create mode 100644 src/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp diff --git a/include/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.h b/include/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.h index 1a5ce22fc..2a675329a 100644 --- a/include/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.h +++ b/include/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.h @@ -16,6 +16,20 @@ class DrawDirectionalStrategy : public DrawStrategyImpl { virtual CalcAheadFunc GetCalcAheadFunc(ParticleManager* pManager); // at 0x18 +private: + void DrawDirectional(const DrawInfo& rInfo, ParticleManager* pManager); + void DrawDirectionalBillboard(const DrawInfo& rInfo, ParticleManager* pManager); + void InitGraphics(const DrawInfo& rInfo, ParticleManager* pManager); + + enum DirectionalPivot { + DIRECTIONAL_PIVOT_NOP, + DIRECTIONAL_PIVOT_BILLBOARD, + DIRECTIONAL_PIVOT__MASK = 0x3 + }; + + DirectionalPivot GetDirectionalPivot(const EmitterDrawSetting& rSetting) { + return static_cast(rSetting.typeOption2 & DIRECTIONAL_PIVOT__MASK); + } }; } // namespace ef diff --git a/src/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp b/src/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp new file mode 100644 index 000000000..9e558db0a --- /dev/null +++ b/src/nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp @@ -0,0 +1,520 @@ +#include +#include + +#include + +namespace nw4r { +namespace ef { + +static u8 directional_tex0_u8[] = {0x00, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, 0x01}; + +DrawDirectionalStrategy::DrawDirectionalStrategy() {} + +inline math::MTX34 CalcLocalTransform(u8 face, f32 px, f32 py, f32 sx, f32 sy, + f32 vs, const nw4r::math::MTX34& rRot) { + switch (face) { + case 0 /* magic number */: { + f32 sx_px = sx * px; + f32 sy_vs = sy * vs; + f32 exp0 = sy * (py + vs - 1.0f); + // clang-format off + return math::MTX34( + rRot._00 * sx, rRot._01 * sy_vs, rRot._02 * sx, -rRot._00 * sx_px - rRot._01 * exp0 + px, + rRot._10 * sx, rRot._11 * sy_vs, rRot._12 * sx, -rRot._10 * sx_px - rRot._11 * exp0 + py, + rRot._20 * sx, rRot._21 * sy_vs, rRot._22 * sx, -rRot._20 * sx_px - rRot._21 * exp0); + // clang-format on + } + default: + case 1 /* magic number */: { + f32 sx_px = sx * px; + f32 sy_py = sy * py; + // clang-format off + return math::MTX34( + rRot._00 * sx, rRot._02 * sy, rRot._01 * sx, -rRot._00 * sx_px - rRot._02 * sy_py + px, + rRot._10 * sx, rRot._12 * sy, rRot._11 * sx, -rRot._10 * sx_px - rRot._12 * sy_py, + rRot._20 * sx, rRot._22 * sy, rRot._21 * sx, -rRot._20 * sx_px - rRot._22 * sy_py - py); + // clang-format on + } + } +} + +inline math::MTX34 CalcRotate(Particle* pParticle, u8 axis) { + math::VEC3 rot; + pParticle->Draw_GetRotate(&rot); + + f32 sx = 0.0f, cx = 1.0f; + f32 sy = 0.0f, cy = 1.0f; + f32 sz = 0.0f, cz = 1.0f; + + f32 sx_sy; + f32 cx_sy; + switch (axis) { + case 0 /* magic number (X axis) */: { + if (rot.x != 0.0f) { + math::SinCosRad(&sx, &cx, -rot.x); + } + // clang-format off + return math::MTX34( + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, cx, -sx, 0.0f, + 0.0f, sx, cx, 0.0f); + // clang-format on + } + case 1 /* magic number (Y axis) */: { + if (rot.y != 0.0f) { + math::SinCosRad(&sy, &cy, -rot.y); + } + // clang-format off + return math::MTX34( + cy, 0.0f, sy, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + -sy, 0.0f, cy, 0.0f); + // clang-format on + } + case 2 /* magic number (Z axis) */: { + if (rot.z != 0.0f) { + math::SinCosRad(&sz, &cz, -rot.z); + } + // clang-format off + return math::MTX34( + cz, -sz, 0.0f, 0.0f, + sz, cz, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f); + // clang-format on + } + default: + case 3 /* magic number (general/Tait-Bryan) */: { + if (rot.x) { + math::SinCosRad(&sx, &cx, -rot.x); + } + if (rot.y) { + math::SinCosRad(&sy, &cy, -rot.y); + } + if (rot.z) { + math::SinCosRad(&sz, &cz, -rot.z); + } + + sx_sy = sx * sy; + cx_sy = cx * sy; + + // clang-format off + return math::MTX34( + cy * cz, sx_sy * cz - cx * sz, cx_sy * cz + sx * sz, 0.0f, + cy * sz, sx_sy * sz + cx * cz, cx_sy * sz - sx * cz, 0.0f, + -sy, cy * sx, cx * cy, 0.0f); + // clang-format on + } + } +} + +static void DrawQuad(const math::MTX34& rMtx, const math::_VEC3* pPosArray, + bool texCoord) { + + math::VEC3 p0, p1, p2, p3; + + math::VEC3Transform(&p0, &rMtx, + static_cast(&pPosArray[0])); + math::VEC3Transform(&p1, &rMtx, + static_cast(&pPosArray[1])); + math::VEC3Transform(&p2, &rMtx, + static_cast(&pPosArray[2])); + math::VEC3Transform(&p3, &rMtx, + static_cast(&pPosArray[3])); + + GXBegin(GX_QUADS, GX_VTXFMT0, 4); + { + GXPosition(p0); + if (texCoord) { + GXTexCoord1x8(0); + } + + GXPosition(p1); + if (texCoord) { + GXTexCoord1x8(1); + } + + GXPosition(p2); + if (texCoord) { + GXTexCoord1x8(2); + } + + GXPosition(p3); + if (texCoord) { + GXTexCoord1x8(3); + } + } + GXEnd(); +} + +static void DrawQuad(const math::VEC3& p, const math::VEC3& d0, + const math::VEC3& d1, bool texCoord) { + GXBegin(GX_QUADS, GX_VTXFMT0, 4); + { + GXPosition(p - d0); + if (texCoord) { + GXTexCoord1x8(0); + } + + GXPosition(p - d1); + if (texCoord) { + GXTexCoord1x8(1); + } + + GXPosition(p + d0); + if (texCoord) { + GXTexCoord1x8(2); + } + + GXPosition(p + d1); + if (texCoord) { + GXTexCoord1x8(3); + } + } + GXEnd(); +} + +void DrawDirectionalStrategy::Draw(const DrawInfo& rInfo, + ParticleManager* pManager) { + InitGraphics(rInfo, pManager); + + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + switch (GetDirectionalPivot(rSetting)) { + default: + case DIRECTIONAL_PIVOT_NOP: { + DrawDirectional(rInfo, pManager); + break; + } + case DIRECTIONAL_PIVOT_BILLBOARD: { + DrawDirectionalBillboard(rInfo, pManager); + break; + } + } +} + +void DrawDirectionalStrategy::DrawDirectional(const DrawInfo& rInfo, + ParticleManager* pManager) { + InitGraphics(rInfo, pManager); + + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + AheadContext aheadContext(*rInfo.GetViewMtx(), pManager); + CalcAheadFunc pCalcAheadFunc = GetCalcAheadFunc(pManager); + + math::MTX34 glbMtx; + math::MTX34 posMtx; + + math::VEC3 refYAxis; + math::VEC3 refZAxis; + + pManager->CalcGlobalMtx(&glbMtx); + math::MTX34Mult(&posMtx, rInfo.GetViewMtx(), &glbMtx); + GXLoadPosMtxImm(posMtx, GX_PNMTX0); + + math::MTX34 emMtx; + math::MTX34 locMtx; + + pManager->mManagerEM->CalcGlobalMtx(&emMtx); + math::MTX34Inv(&locMtx, &glbMtx); + math::MTX34Mult(&emMtx, &locMtx, &emMtx); + + refYAxis = math::VEC3(emMtx._01, emMtx._11, emMtx._21); + refZAxis = math::VEC3(emMtx._02, emMtx._12, emMtx._22); + Normalize(&refZAxis); + + for (Particle* pParticle = GetYoungestParticle(pManager); + pParticle != NULL && pParticle->mPrevAxis.x > 1.0f; + pParticle = GetElderParticle(pManager, pParticle)) { + pParticle->mPrevAxis = refYAxis; + } + + f32 px = rSetting.pivotX / 100.0f; + f32 py = rSetting.pivotY / 100.0f; + + GetFirstDrawParticleFunc pGetFirstFunc = GetGetFirstDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + GetNextDrawParticleFunc pGetNextFunc = GetGetNextDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + bool first = true; + + for (Particle* pParticle = pGetFirstFunc(pManager); pParticle; + pParticle = pGetNextFunc(pManager, pParticle)) { + + f32 sx = pParticle->Draw_GetSizeX(); + if (sx < std::numeric_limits::epsilon()) { + continue; + } + + f32 sy = pParticle->Draw_GetSizeY(); + if (sy < std::numeric_limits::epsilon()) { + continue; + } + + f32 vs = 1.0f; + if (rSetting.typeOption0) { + math::VEC3 v; + pParticle->GetMoveDir(&v); + + vs += 0.5f * math::FSqrt(math::VEC3Dot(&v, &v)) / sy; + } + + SetupGP(pParticle, rSetting, rInfo, first, false); + first = false; + + math::MTX34 rotMtx = CalcRotate(pParticle, rSetting.typeAxis); + math::MTX34 locMtx = CalcLocalTransform(rSetting.typeOption1, px, py, + sx, sy, vs, rotMtx); + math::VEC3 xAxis; + math::VEC3 yAxis; + math::VEC3 zAxis; + pCalcAheadFunc(&yAxis, &aheadContext, pParticle); + + math::VEC3Cross(&zAxis, &pParticle->mPrevAxis, &yAxis); + if (!Normalize(&zAxis)) { + zAxis = refZAxis; + } + + math::VEC3Cross(&xAxis, &yAxis, &zAxis); + pParticle->mPrevAxis = xAxis; + math::VEC3 pos(pParticle->mParameter.mPosition); + // clang-format off + math::MTX34 mtxParticle( + xAxis.x, yAxis.x, zAxis.x, pos.x, + xAxis.y, yAxis.y, zAxis.y, pos.y, + xAxis.z, yAxis.z, zAxis.z, pos.z); + // clang-format on + math::MTX34 mtx; + math::MTX34Mult(&mtx, &mtxParticle, &locMtx); + + // clang-format off + static const math::_VEC3 p[4] = { + -1.0f, -1.0f, 0.0f, + -1.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, + 1.0f, -1.0f, 0.0f + }; + // clang-format on + + DrawQuad(mtx, p, mNumTexmap > 0); + + if (rSetting.typeOption == EmitterDrawSetting::TYPE_CMN_CROSS) { + // clang-format off + static const math::_VEC3 px[4] = { + 0.0f, -1.0f, 1.0f, + 0.0f, 1.0f, 1.0f, + 0.0f, 1.0f, -1.0f, + 0.0f, -1.0f, -1.0f + }; + // clang-format on + + DrawQuad(mtx, px, mNumTexmap > 0); + } + } +} + +void DrawDirectionalStrategy::DrawDirectionalBillboard( + const DrawInfo& rInfo, ParticleManager* pManager) { + InitGraphics(rInfo, pManager); + + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + AheadContext aheadContext(*rInfo.GetViewMtx(), pManager); + CalcAheadFunc pCalcAheadFunc = GetCalcAheadFunc(pManager); + + math::MTX34 viewMtx; + math::MTX34 glbMtx; + pManager->CalcGlobalMtx(&glbMtx); + math::MTX34Mult(&viewMtx, rInfo.GetViewMtx(), &glbMtx); + GXLoadPosMtxImm(mIdentityMtx, GX_PNMTX0); + + f32 vx = math::FSqrt(viewMtx._00 * viewMtx._00 + + viewMtx._10 * viewMtx._10 + + viewMtx._20 * viewMtx._20); + + f32 vy = math::FSqrt(viewMtx._01 * viewMtx._01 + + viewMtx._11 * viewMtx._11 + + viewMtx._21 * viewMtx._21); + + f32 vz = math::FSqrt(viewMtx._02 * viewMtx._02 + + viewMtx._12 * viewMtx._12 + + viewMtx._22 * viewMtx._22); + + for (Particle* pParticle = GetYoungestParticle(pManager); + pParticle != NULL && pParticle->mPrevAxis.x > 1.0f; + pParticle = GetElderParticle(pManager, pParticle)) { + pParticle->mPrevAxis = mZeroVec; + } + + f32 px = rSetting.pivotX / 100.0f; + f32 py = rSetting.pivotY / 100.0f; + + GetFirstDrawParticleFunc pGetFirstFunc = GetGetFirstDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + GetNextDrawParticleFunc pGetNextFunc = GetGetNextDrawParticleFunc( + rSetting.mFlags & EmitterDrawSetting::FLAG_DRAW_ORDER); + + bool first = true; + + for (Particle* pParticle = pGetFirstFunc(pManager); pParticle != NULL; + pParticle = pGetNextFunc(pManager, pParticle)) { + f32 sx = pParticle->Draw_GetSizeX(); + if (sx < NW4R_MATH_FLT_EPSILON) { + continue; + } + + f32 sy = pParticle->Draw_GetSizeY(); + if (sy < NW4R_MATH_FLT_EPSILON) { + continue; + } + + f32 vs = 1.0f; + if (rSetting.typeOption0) { + math::VEC3 v; + pParticle->GetMoveDir(&v); + + vs += 0.5f * math::FSqrt(math::VEC3Dot(&v, &v)) / sy; + } + + SetupGP(pParticle, rSetting, rInfo, first, false); + first = false; + + math::MTX34 rotMtx = CalcRotate(pParticle, rSetting.typeAxis); + math::MTX34 locMtx = CalcLocalTransform(rSetting.typeOption1, px, py, + sx, sy, vs, rotMtx); + + math::VEC3 xAxis; + math::VEC3 yAxis; + math::VEC3 zAxis; + + math::VEC3 dir; + pCalcAheadFunc(&dir, &aheadContext, pParticle); + math::VEC3TransformNormal(&dir, &viewMtx, &dir); + + yAxis = dir; + Normalize(&yAxis); + + xAxis = math::VEC3(dir.y, -dir.x, 0.0f); + + if (Normalize(&xAxis)) { + zAxis = math::VEC3( + -dir.x * dir.z, -dir.y * dir.z, + dir.x * dir.x + dir.y * dir.y); + } else { + math::VEC3Cross(&xAxis, &dir, &pParticle->mPrevAxis); + if (!Normalize(&xAxis)) { + xAxis = mXUnitVec; + } + + math::VEC3Cross(&zAxis, &xAxis, &dir); + } + Normalize(&zAxis); + + pParticle->mPrevAxis = zAxis; + + math::VEC3 pos; + math::VEC3Transform(&pos, &viewMtx, + &pParticle->mParameter.mPosition); + + math::MTX34 mtxParticle( + vx * xAxis.x, vy * yAxis.x, vz * zAxis.x, pos.x, + vx * xAxis.y, vy * yAxis.y, vz * zAxis.y, pos.y, + vx * xAxis.z, vy * yAxis.z, vz * zAxis.z, pos.z); + + math::MTX34 mtx; + math::MTX34Mult(&mtx, &mtxParticle, &locMtx); + + math::VEC3 vp0(mtx._03, mtx._13, mtx._23); + + math::VEC3 vd0(mtx._00 + mtx._01, + mtx._10 + mtx._11, + mtx._20 + mtx._21); + math::VEC3 vd1(mtx._00 - mtx._01, + mtx._10 - mtx._11, + mtx._20 - mtx._21); + DrawQuad(vp0, vd0, vd1, mNumTexmap > 0); + + if (rSetting.typeOption == EmitterDrawSetting::TYPE_CMN_CROSS) { + math::VEC3 vd2(mtx._01 + mtx._02, + mtx._11 + mtx._12, + mtx._21 + mtx._22); + math::VEC3 vd3(mtx._01 - mtx._02, + mtx._11 - mtx._12, + mtx._21 - mtx._22); + DrawQuad(vp0, vd2, vd3, mNumTexmap > 0); + } + + } +} + +void DrawDirectionalStrategy::InitGraphics(const DrawInfo& rInfo, + ParticleManager* pManager) { + + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + InitTexture(rSetting); + InitTev(rSetting, rInfo); + InitColor(pManager, rSetting, rInfo); + + GXEnableTexOffsets(GX_TEXCOORD0, TRUE, TRUE); + + GXSetArray(GX_VA_TEX0, directional_tex0_u8, 2); + + GXClearVtxDesc(); + GXSetVtxDesc(GX_VA_POS, GX_DIRECT); + + if (mNumTexmap > 0) { + GXSetVtxDesc(GX_VA_TEX0, GX_INDEX8); + } + + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0); + GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); + + GXSetCurrentMtx(GX_PNMTX0); +} + +DrawStrategyImpl::CalcAheadFunc +DrawDirectionalStrategy::GetCalcAheadFunc(ParticleManager* pManager) { + + const EmitterDrawSetting& rSetting = + *pManager->mResource->GetEmitterDrawSetting(); + + switch (rSetting.typeDir) { + case EmitterDrawSetting::AHEAD_BB_SPEED: { + return CalcAhead_Speed; + } + + case EmitterDrawSetting::AHEAD_BB_EMITTER_CENTER: { + return CalcAhead_EmitterCenter; + } + + case EmitterDrawSetting::AHEAD_BB_EMITTER_DESIGN: { + return CalcAhead_EmitterDesign; + } + + case EmitterDrawSetting::AHEAD_BB_PARTICLE: { + return CalcAhead_Particle; + } + + case EmitterDrawSetting::AHEAD_CMN_NODESIGN: { + return CalcAhead_NoDesign; + } + + case EmitterDrawSetting::AHEAD_BB_PARTICLE_BOTH: { + return CalcAhead_ParticleBoth; + } + + case EmitterDrawSetting::AHEAD_CMN_NODESIGN + 1 /* magic number */: + default: { + return CalcAhead_Speed; + } + } +} + +} // namespace ef +} // namespace nw4r From 337fa49898b0fc5b8c89928fe41766c81cd88584 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:54:14 +0300 Subject: [PATCH 6/9] Linked `ef_particlemanager.cpp` --- configure.py | 2 +- include/nw4r/ef/ef_random.h | 29 +- include/nw4r/ef/ef_res_animcurve.h | 27 +- src/nw4r/ef/ef_particlemanager.cpp | 701 +++++++++++++++-------------- 4 files changed, 400 insertions(+), 359 deletions(-) diff --git a/configure.py b/configure.py index 687dc7f2c..42e1e0031 100755 --- a/configure.py +++ b/configure.py @@ -502,7 +502,7 @@ def MatchingFor(*versions): Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_emitter.cpp"), Object(NonMatching, "nw4r/ef/ef_animcurve.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_particle.cpp"), - Object(NonMatching, "nw4r/ef/ef_particlemanager.cpp"), + Object(Matching, "nw4r/ef/ef_particlemanager.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_resource.cpp"), Object(Matching, "nw4r/ef/ef_util.cpp"), Object(Matching, "nw4r/ef/ef_handle.cpp"), diff --git a/include/nw4r/ef/ef_random.h b/include/nw4r/ef/ef_random.h index 9bda269b1..35a93703c 100644 --- a/include/nw4r/ef/ef_random.h +++ b/include/nw4r/ef/ef_random.h @@ -5,6 +5,33 @@ namespace nw4r { namespace ef { +enum { + // tentative names + RANDOM_MULT = 0x343FD, + RANDOM_ADD = 0x269EC3 // perhaps RANDOM_INC would be a better name for the LCG increment? +}; + +inline u32 RANDOM_NEXT(u32 prev) { + // from TFP2 + return prev * RANDOM_MULT + RANDOM_ADD; +} + +inline u32 RANDOM_INIT(u16 objectSeed, u16 keySeed, u16 keyIdx, u32 keyDiv) { + // from TFP2 + union { + u32 whole; + u8 part[4]; + } result; + + result.whole = objectSeed * 0x3F81F635 + keySeed * 0x30A74193 + keyDiv * 0x7B929 + keyIdx * 0x371097E7 + 0x4BF53; //TODO: clean up magic numbers + + result.part[2] ^= result.part[3]; + result.part[1] ^= result.part[2]; + result.part[0] ^= result.part[1]; + + return result.whole; +} + class Random { private: u32 mSeed; // at 0x0 @@ -26,7 +53,7 @@ class Random { private: void MixRandomSeed() { - mSeed = mSeed * 0x343FD + 0x269EC3; + mSeed = mSeed * RANDOM_MULT + RANDOM_ADD; } }; diff --git a/include/nw4r/ef/ef_res_animcurve.h b/include/nw4r/ef/ef_res_animcurve.h index f2ccb3b79..34a59eff6 100644 --- a/include/nw4r/ef/ef_res_animcurve.h +++ b/include/nw4r/ef/ef_res_animcurve.h @@ -34,7 +34,7 @@ enum AnimCurveTarget { // AC_TYPE_PARTICLE_F32 AC_TARGET_SIZE, AC_TARGET_SCALE, - AC_TARGET_12, + AC_TARGET_ROTATE, AC_TARGET_TEXTURE1SCALE = 17, AC_TARGET_TEXTURE1ROTATE, AC_TARGET_TEXTURE1TRANSLATE, @@ -45,13 +45,13 @@ enum AnimCurveTarget { AC_TARGET_TEXTUREINDROTATE, AC_TARGET_TEXTUREINDTRANSLATE, - AC_TARGET_UNK32 = 32, + AC_TARGET_FIELD_GRAVITY = 32, AC_TARGET_FIELD_SPEED, - AC_TARGET_UNK34, - AC_TARGET_UNK35, - AC_TARGET_UNK36, - AC_TARGET_UNK38 = 38, // FieldSpin? - AC_TARGET_UNK39, + AC_TARGET_FIELD_MAGNET, + AC_TARGET_FIELD_NEWTON, + AC_TARGET_FIELD_VORTEX, + AC_TARGET_FIELD_SPIN = 38, + AC_TARGET_FIELD_RANDOM, AC_TARGET_EMIT_COMMONPARAM = 64, AC_TARGET_EMIT_SCALE, AC_TARGET_EMIT_ROTATE, @@ -63,9 +63,6 @@ enum AnimCurveTarget { AC_TARGET_EMIT_SPEED_SPECDIR, AC_TARGET_EMIT_EMISSION, - // AC_TYPE_PARTICLE_ROTATE - AC_TARGET_ROTATE = 12, - // Texture target // AC_TARGET_TEXTURE1 = 23, // AC_TARGET_TEXTURE2 = 24, @@ -74,16 +71,6 @@ enum AnimCurveTarget { // Child target // AC_TARGET_CHILD = 26, - // Field target - // AC_TARGET_FIELD_GRAVITY = 27, - // AC_TARGET_FIELD_SPEED = 28, - // AC_TARGET_FIELD_MAGNET = 29, - // AC_TARGET_FIELD_NEWTON = 30, - // AC_TARGET_FIELD_VORTEX = 31, - // AC_TARGET_FIELD_SPIN = 32, - // AC_TARGET_FIELD_RANDOM = 33, - // AC_TARGET_FIELD_TAIL = 34, - // Postfield target // AC_TARGET_POSTFIELD_SIZE = 35, // AC_TARGET_POSTFIELD_ROTATE = 36, diff --git a/src/nw4r/ef/ef_particlemanager.cpp b/src/nw4r/ef/ef_particlemanager.cpp index 9b0528dad..0af2d3486 100644 --- a/src/nw4r/ef/ef_particlemanager.cpp +++ b/src/nw4r/ef/ef_particlemanager.cpp @@ -143,26 +143,23 @@ void ParticleManager::Calc() { math::MTX34 mtxEmToGlb; mManagerEM->CalcGlobalMtx(&mtxEmToGlb); - math::MTX34 mtxEmToLoc;//0x2f0? + math::MTX34 mtxEmToLoc; math::MTX34Mult(&mtxEmToLoc, &mtxGlbToLoc, &mtxEmToGlb); - math::MTX34 mtxLocToEm;//0x2c0? + math::MTX34 mtxLocToEm; math::MTX34Inv(&mtxLocToEm, &mtxEmToLoc); - math::MTX34 mtxGlbToLocNoTrans = mtxGlbToLoc; + math::MTX34 mtxGlbToLocNoTrans(mtxGlbToLoc); mtxGlbToLocNoTrans._03 = 0.0f; mtxGlbToLocNoTrans._13 = 0.0f; mtxGlbToLocNoTrans._23 = 0.0f; - math::MTX34 mtxLocToGlbNoTrans; - math::MTX34Inv(&mtxLocToGlbNoTrans, &mtxGlbToLocNoTrans); - - math::MTX34 mtxLocToEmNoTrans = mtxLocToEm; + math::MTX34 mtxLocToEmNoTrans(mtxLocToEm); mtxLocToEmNoTrans._03 = 0.0f; mtxLocToEmNoTrans._13 = 0.0f; mtxLocToEmNoTrans._23 = 0.0f; - math::MTX34 mtxEmToLocNoTrans = mtxEmToLoc; + math::MTX34 mtxEmToLocNoTrans(mtxEmToLoc); mtxEmToLocNoTrans._03 = 0.0f; mtxEmToLocNoTrans._13 = 0.0f; mtxEmToLocNoTrans._23 = 0.0f; @@ -187,8 +184,8 @@ void ParticleManager::Calc() { mManagerEM->mManagerEF->SetFlagExistCalcRemain(true); } - math::VEC3 prevPos = pIt->mParameter.mPosition;//0x138 - math::VEC3 prevVel = pIt->mParameter.mVelocity;//0x12c + math::VEC3 prevPos = pIt->mParameter.mPosition; + math::VEC3 prevVel = pIt->mParameter.mVelocity; math::VEC3 prevDir; pIt->GetMoveDir(&prevDir); @@ -202,7 +199,6 @@ void ParticleManager::Calc() { math::VEC3 addVel(0.0f, 0.0f, 0.0f); math::VEC3 addPos(0.0f, 0.0f, 0.0f); - math::VEC3 affect(0.0f, 0.0f, 0.0f); bool findEmitterTiming = false; @@ -211,8 +207,7 @@ void ParticleManager::Calc() { u8* pPtclTrack = mResource->GetPtclTrack(i); -#define pTrackAsHeader reinterpret_cast(pPtclTrack) - + AnimCurveHeader* pTrackAsHeader = reinterpret_cast(pPtclTrack); if (pTrackAsHeader->processFlag & AnimCurveHeader::PROC_FLAG_STOP) { continue; } @@ -253,10 +248,10 @@ void ParticleManager::Calc() { } // Load curveFlag and kindEnable - u16 ctrl = *reinterpret_cast(&pTrackAsHeader->curveFlag); + u8 ctrl = *reinterpret_cast(&pTrackAsHeader->curveFlag) >> 8; u8 kind = pTrackAsHeader->kindType; - switch (ctrl >> 4) { + switch (ctrl) { case AC_TYPE_PARTICLE_U8: { u8* pTarget; @@ -311,6 +306,10 @@ void ParticleManager::Calc() { &pIt->mParameter.mColor[COLOR_LAYER_1][COLOR_IDX_SEC].r; break; } + + default: { + continue; + } } AnimCurveExecuteU8(pPtclTrack, pTarget, tick, seed, life); @@ -394,340 +393,369 @@ void ParticleManager::Calc() { } case AC_TARGET_FIELD_SPEED: { + math::VEC3 affect; f32 speed; AnimCurveExecuteF32(pPtclTrack, &speed, tick, seed, life); - math::VEC3Scale(&affect, &affect, speed - 1.0f); + math::VEC3Scale(&affect, &prevVel, speed - 1.0f); addVel += affect; break; } - case AC_TARGET_UNK32: { - /*f32 f1;rangeTable; // at 0x10 - u32 randomTable; // at 0x14 - u32 nameTable; - pTrackAsHeader->keyTable - +pTrackAsHeader->rangeTable+randomTable+nameTable - */ - u8 *r16 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - f32 stack_0x10 = *reinterpret_cast(r16 + 0x20 + 0x0); - AnimCurveExecuteF32(pPtclTrack, &stack_0x10, tick, seed, life); - math::VEC3 stack_0xf0;// = *reinterpret_cast(r16 + 0x20 + 0x4) * f1; - stack_0xf0.x = *reinterpret_cast(r16 + 0x20 + 0x4) * stack_0x10; - stack_0xf0.y = *reinterpret_cast(r16 + 0x20 + 0x8) * stack_0x10; - stack_0xf0.z = *reinterpret_cast(r16 + 0x20 + 0xC) * stack_0x10; - if (*reinterpret_cast(r16 + 0x20 + 0x10)) math::VEC3Transform(&stack_0xf0,&mtxLocToGlb,&stack_0xf0); - else math::VEC3Transform(&stack_0xf0,&mtxGlbToLoc,&stack_0xf0); - switch((r16 + 0x20)[0x11]) { - case 0: { - addVel += stack_0xf0; - break; - } - case 1: { - addPos += stack_0xf0; - break; - } - } + case AC_TARGET_FIELD_GRAVITY: { + struct InfoFieldGravity { + float power; // at 0x0 + math::VEC3 rotate; // at 0x4 + bool emitterSpace; // at 0x10 + u8 addTarget; // at 0x11 + } *pInfo = reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + f32 power = pInfo->power; + AnimCurveExecuteF32(pPtclTrack, &power, tick, seed, life); + + math::VEC3 affect; + affect.x = pInfo->rotate.x * power; + affect.y = pInfo->rotate.y * power; + affect.z = pInfo->rotate.z * power; + + if (pInfo->emitterSpace) { + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + } else { + math::VEC3Transform(&affect, &mtxGlbToLocNoTrans, &affect); + } + + switch (pInfo->addTarget) { + case 0 /* magic number (velocity) */: { + addVel += affect; + break; + } + case 1 /* magic number (position) */: { + addPos += affect; + break; + } + } + break; + } + + case AC_TARGET_FIELD_RANDOM: { + struct InfoFieldRandom { + f32 power; // at 0x0 + f32 diffusion; // at 0x4 + u16 interval; // at 0x8 + u8 option; // at 0xA + u8 addTarget; // at 0xB + } *pInfo = reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + u32 rand = RANDOM_INIT(seed, pTrackAsHeader->randomSeed, tick, 0); + if (!(pTrackAsHeader->processFlag & AnimCurveHeader::PROC_FLAG_TIMING)) { + if (tick == 0) { + break; + } + if (tick % (pInfo->interval + 1) != 0) { + break; + } + } else { + if (pIt->mTick != 0 && tick % (pInfo->interval + 1) != 0) { + break; + } + } + + f32 power = pInfo->power; + AnimCurveExecuteF32(pPtclTrack, &power, tick, seed, life); + + math::VEC3 affect; + if (pInfo->option & 2 /* magic number */) { + // rolls from the range [-1, 1) + affect.x = static_cast((s16)(rand >> 16)) / 0x8000 * power; + rand = RANDOM_NEXT(rand); + affect.y = static_cast((s16)(rand >> 16)) / 0x8000 * power; + rand = RANDOM_NEXT(rand); + affect.z = static_cast((s16)(rand >> 16)) / 0x8000 * power; + } else { + math::VEC3 dir; + if (pIt->mTick == 0) { + dir = prevVel; + } else { + dir = prevDir; + } + math::VEC3Transform(&dir, &mtxLocToEmNoTrans, &dir); + if (math::VEC3LenSq(&dir) < NW4R_MATH_FLT_MIN) { + dir.y = 1.0f; + } else { + math::VEC3Normalize(&dir, &dir); + } + + math::MTX34 mtx; + GetDirMtxY(&mtx, dir); + + if (pInfo->diffusion != 0.0f) { + // rolls from the range [0, 1] instead of [0, 1) for some reason + f32 yrot = static_cast((u16)(rand >> 16)) / 0xFFFF * pInfo->diffusion; + rand = RANDOM_NEXT(rand); + f32 xrot = static_cast((u16)(rand >> 16)) / 0xFFFF * NW4R_MATH_PI * 2.0f; + affect.x = std::sinf(yrot) * std::sinf(xrot); + affect.y = std::cosf(yrot); + affect.z = std::sinf(yrot) * std::cosf(xrot); + rand = RANDOM_NEXT(rand); + math::VEC3Scale(&affect, &affect, static_cast((u16)(rand >> 16)) / 0xFFFF * power); + math::VEC3Transform(&affect, &mtx, &affect); + } else { + // rolls from the range [-1, 1) instead of [0, 1), so the direction can flip between ticks if the diffusion is 0 + affect.x = 0.0f; + affect.y = static_cast((s16)(rand >> 16)) / 0x8000 * power; + affect.z = 0.0f; + math::VEC3Transform(&affect, &mtx, &affect); + } + } + + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + switch (pInfo->addTarget) { + case 0 /* magic number (velocity) */: { + addVel += affect; + break; + } + case 1 /* magic number (position) */: { + addPos += affect; + break; + } + } break; } - case AC_TARGET_UNK39: { - // 80019fac - // r5:tick, r11:pTrackAsHeader->processFlag&AnimCurveHeader::PROC_FLAG_TIMING - u8 *r28 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - Random stack_0x8; - *((u32 *)&stack_0x8) = pIt->mRandSeed * 0x3F81F635 + - pTrackAsHeader->randomSeed * 0x30A74193 + - tick * 0x371097E7 + /*0x50000 - 0x40AD*/0x4BF53; - ((u8*)&stack_0x8)[2] ^= ((u8*)&stack_0x8)[3]; - ((u8*)&stack_0x8)[1] ^= ((u8*)&stack_0x8)[2]; - ((u8*)&stack_0x8)[0] ^= ((u8*)&stack_0x8)[1]; - if (pTrackAsHeader->processFlag&AnimCurveHeader::PROC_FLAG_TIMING) { - if (tick == 0 || tick % (*reinterpret_cast(r28 + 0x20 + 0x8) + 1) != 0) { - break; - } - } else { - if (tick != 0 && tick % (*reinterpret_cast(r28 + 0x20 + 0x8) + 1) != 0) { - break; - } - } - f32 stack_0xc = *reinterpret_cast(r28 + 0x20 + 0x0); - AnimCurveExecuteF32(pPtclTrack, &stack_0xc, tick, seed, life); - math::VEC3 stack_0xd8; - math::VEC3 stack_0xe4; - math::MTX34 stack_0x200; - if (*reinterpret_cast(r28 + 0x20 + 0xA) & 2 /* magic number */) { - f32 f2 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; - stack_0xe4.x = f2 * stack_0xc; - f32 f1 = stack_0x8.RandFloat(); f1 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; - stack_0xe4.y = f1 * stack_0xc; - f32 f0 = stack_0x8.RandFloat(); f0 = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; - stack_0xe4.z = f0 * stack_0xc; - } else { - if (pIt->mTick == 0) { - stack_0xd8 = affect/*?*/; - } else { - stack_0xd8 = addVel/*?*/; - } - math::VEC3Transform(&stack_0xd8,&mtxLocToGlb,&stack_0xd8); - if (math::VEC3LenSq(&stack_0xd8) < 0x8000) { - stack_0xd8.y = 1.0f; - } else { - math::VEC3Normalize(&stack_0xd8, &stack_0xd8); - } - GetDirMtxY(&stack_0x200, stack_0xd8); - if (0.0f != *reinterpret_cast(r28 + 0x20 + 0x4)) { - f32 f1 = static_cast((u16)(*(u32*)&stack_0x8 >> 16)) / 0x10000; - f32 f0 = stack_0x8.RandFloat(); - //f27:PI,f28:2.0f - f32 yrot = f1 * *reinterpret_cast(r28 + 0x20 + 0x4); - f32 xrot = 2.0f * (M_PI * f0); - stack_0xe4.x = std::sinf(yrot) * std::sinf(xrot); - stack_0xe4.y = std::cosf(yrot); - stack_0xe4.z = std::sinf(yrot) * std::cosf(xrot); - math::VEC3Scale(&stack_0xe4, &stack_0xe4, stack_0x8.RandFloat() * stack_0xc); - math::VEC3Transform(&stack_0xe4,&stack_0x200,&stack_0xe4); - } else { - stack_0xe4.x = 0.0f; - stack_0xe4.y = static_cast((s16)(*(u32*)&stack_0x8 >> 16)) * stack_0xc; - stack_0xe4.z = 0.0f; - math::VEC3Transform(&stack_0xe4,&stack_0x200,&stack_0xe4); - } - } - math::VEC3Transform(&stack_0xe4,&mtxLocToGlb,&stack_0xe4); - switch((r28 + 0x20)[0xB]) { - case 0: { - addVel += stack_0xe4; - break; - } - case 1: { - addPos += stack_0xe4; - break; - } - } + case AC_TARGET_FIELD_SPIN: { + struct InfoFieldSpin { + f32 speed; // at 0x0 + math::VEC3 rotate; // at 0x4 + bool emitterSpace; // at 0x10 + u8 addTarget; // at 0x11 + char UNK_0x12[2]; + } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + AnimCurveExecuteF32(pPtclTrack, &infoCopy.speed, tick, seed, life); + + math::VEC3 forceDir; + Rotation2VecY(infoCopy.rotate, &forceDir); + + math::MTX34 mtx; + math::MTX34RotAxisRad(&mtx, &forceDir, infoCopy.speed); + + math::VEC3 particlePos; + if (infoCopy.emitterSpace) { + math::VEC3Transform(&particlePos, &mtxLocToEm, &prevPos); + } else { + math::VEC3Transform(&particlePos, &mtxLocToGlb, &prevPos); + + math::VEC3 emitterPos; + emitterPos.x = 0.0f; + emitterPos.y = 0.0f; + emitterPos.z = 0.0f; + math::VEC3Transform(&emitterPos, &mtxEmToGlb, &emitterPos); + + math::VEC3Sub(&particlePos, &particlePos, &emitterPos); + } + + math::VEC3 affect; + math::VEC3Transform(&affect, &mtx, &particlePos); + math::VEC3Sub(&affect, &affect, &particlePos); + + if (infoCopy.emitterSpace) { + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + } else { + math::VEC3Transform(&affect, &mtxGlbToLocNoTrans, &affect); + } + + switch (infoCopy.addTarget) { + case 0 /* magic number (position) */: { + addVel += affect; + break; + } + case 1 /* magic number (velocity) */: { + addPos += affect; + break; + } + } break; } - case AC_TARGET_UNK38: { - // 8001a364 - struct UNKSTRUCT_UNK38 { - f32 FLOAT_0x0; - math::VEC3 VECTOR_0x4; - u8 BYTE_0x10; - u8 BYTE_0x11; - u8 BYTE_0x12; - u8 BYTE_0x13; - } stack_0x18c; - u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - stack_0x18c = *(UNKSTRUCT_UNK38*)(r8 + 0x20); - AnimCurveExecuteF32(pPtclTrack, &stack_0x18c.FLOAT_0x0, tick, seed, life); - math::VEC3 stack_0xcc; - math::VEC3 stack_0xc0; - math::VEC3 stack_0xb4; - math::VEC3 stack_0xa8; - Rotation2VecY(stack_0x18c.VECTOR_0x4, &stack_0xcc); - math::MTX34 stack_0x1d0; - math::MTX34RotAxisRad(&stack_0x1d0, &stack_0xcc, stack_0x18c.FLOAT_0x0); - if (stack_0x18c.BYTE_0x10) { - math::VEC3Transform(&stack_0xc0,&mtxGlbToLoc,&prevPos); - } else { - math::VEC3Transform(&stack_0xc0,&mtxLocToGlb,&prevPos); - stack_0xb4.x = 0.0f; - stack_0xb4.y = 0.0f; - stack_0xb4.z = 0.0f; - math::VEC3Transform(&stack_0xb4,&mtxLocToGlb,&stack_0xb4); - math::VEC3Sub(&stack_0xc0,&stack_0xc0,&stack_0xb4); - } - math::VEC3Transform(&stack_0xa8,&stack_0x1d0,&stack_0xc0); - math::VEC3Sub(&stack_0xa8,&stack_0xa8,&stack_0xc0); - if (stack_0x18c.BYTE_0x10) { - math::VEC3Transform(&stack_0xa8,&mtxGlbToLoc,&stack_0xa8); - } else { - math::VEC3Transform(&stack_0xa8,&mtxLocToGlb,&stack_0xa8); - } - switch (stack_0x18c.BYTE_0x11) { - case 0: { - addVel += stack_0xa8; - break; - } - case 1: { - addPos += stack_0xa8; - break; - } - } - break; - } - - case AC_TARGET_UNK34: { - // 8001a51c - struct UNKSTRUCT_UNK34 { - f32 FLOAT_0x0; - math::VEC3 VECTOR_0x4; - u8 BYTE_0x10; - u8 BYTE_0x11; - u8 BYTE_0x12; - u8 BYTE_0x13; - } stack_0x178; - u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - stack_0x178 = *(UNKSTRUCT_UNK34*)(r8 + 0x20); - AnimCurveExecuteF32(pPtclTrack, &stack_0x178.FLOAT_0x0, tick, seed, life); - math::VEC3 stack_0x9c; - math::VEC3 stack_0x90; - math::VEC3 stack_0x84; - if (stack_0x178.BYTE_0x10) { - math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); - } else { - math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); - stack_0x90.x = 0.0f; - stack_0x90.y = 0.0f; - stack_0x90.z = 0.0f; - math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); - math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); - } - math::VEC3Sub(&stack_0x84,&stack_0x178.VECTOR_0x4,&stack_0x9c); - if (0.0f != stack_0x84.x || 0.0f != stack_0x84.y || 0.0f != stack_0x84.z) { - math::VEC3Normalize(&stack_0x84,&stack_0x84); - } - stack_0x84 *= stack_0x178.FLOAT_0x0; - if (stack_0x178.BYTE_0x10) { - math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); - } else { - math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); - } - switch (stack_0x178.BYTE_0x11) { - case 0: { - addVel += stack_0x84; - break; - } - case 1: { - addPos += stack_0x84; - break; - } - } + case AC_TARGET_FIELD_MAGNET: { + struct InfoFieldMagnet { + f32 power; // at 0x0 + math::VEC3 translate; // at 0x4 + bool emitterSpace; // at 0x10 + u8 addTarget; // at 0x11 + char UNK_0x12[2]; + } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + AnimCurveExecuteF32(pPtclTrack, &infoCopy.power, tick, seed, life); + + math::VEC3 particlePos; + if (infoCopy.emitterSpace) { + math::VEC3Transform(&particlePos, &mtxLocToEm, &prevPos); + } else { + math::VEC3Transform(&particlePos, &mtxLocToGlb, &prevPos); + + math::VEC3 emitterPos; + emitterPos.x = 0.0f; + emitterPos.y = 0.0f; + emitterPos.z = 0.0f; + math::VEC3Transform(&emitterPos, &mtxEmToGlb, &emitterPos); + + math::VEC3Sub(&particlePos, &particlePos, &emitterPos); + } + + math::VEC3 affect; + math::VEC3Sub(&affect, &infoCopy.translate, &particlePos); + if (affect.x != 0.0f || affect.y != 0.0f || affect.z != 0.0f) { + math::VEC3Normalize(&affect, &affect); + } + math::VEC3Scale(&affect, &affect, infoCopy.power); + if (infoCopy.emitterSpace) { + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + } else { + math::VEC3Transform(&affect, &mtxGlbToLocNoTrans, &affect); + } + + switch (infoCopy.addTarget) { + case 0 /* magic number (position) */: { + addVel += affect; + break; + } + case 1 /* magic number (velocity) */: { + addPos += affect; + break; + } + } break; } - case AC_TARGET_UNK35: {//Newton? - // 8001a6f0 - struct UNKSTRUCT_UNK35 { - f32 FLOAT_0x0; - f32 FLOAT_0x4; - math::VEC3 VECTOR_0x8; - u8 BYTE_0x14; - u8 BYTE_0x15; - u8 BYTE_0x16; - u8 BYTE_0x17; - } infoCopy; - u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - infoCopy = *(UNKSTRUCT_UNK35*)(r8 + 0x20); - AnimCurveExecuteF32(pPtclTrack, &infoCopy.FLOAT_0x0, tick, seed, life); - /**/ - math::VEC3 stack_0x9c; - math::VEC3 stack_0x90; - math::VEC3 stack_0x84; - if (infoCopy.BYTE_0x14) { - math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); - } else { - math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); - stack_0x90.x = 0.0f; - stack_0x90.y = 0.0f; - stack_0x90.z = 0.0f; - math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); - math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); - } - math::VEC3Sub(&stack_0x84,&infoCopy.VECTOR_0x8,&stack_0x9c); - f32 f21 = math::VEC3LenSq(&stack_0x84); - if (0.0f != stack_0x84.x || 0.0f != stack_0x84.y || 0.0f != stack_0x84.z) { - math::VEC3Normalize(&stack_0x84,&stack_0x84); - } - stack_0x84 *= infoCopy.FLOAT_0x0; - f32 f0 = infoCopy.FLOAT_0x4*infoCopy.FLOAT_0x4; - if (f21 > f0) { - f0 /= f21; - stack_0x84 *= f0; - } - if (infoCopy.BYTE_0x14) { - math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); - } else { - math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); - } - switch (infoCopy.BYTE_0x15) { - case 0: { - addVel += stack_0x84; - break; - } - case 1: { - addPos += stack_0x84; - break; - } - } - /**/ + case AC_TARGET_FIELD_NEWTON: { + struct InfoFieldNewton { + f32 power; // at 0x0 + f32 refDistance; // at 0x4 + math::VEC3 translate; // at 0x8 + bool emitterSpace; // at 0x14 + u8 addTarget; // at 0x15 + char UNK_0x16[2]; + } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + AnimCurveExecuteF32(pPtclTrack, &infoCopy.power, tick, seed, life); + + math::VEC3 particlePos; + if (infoCopy.emitterSpace) { + math::VEC3Transform(&particlePos, &mtxLocToEm, &prevPos); + } else { + math::VEC3Transform(&particlePos, &mtxLocToGlb, &prevPos); + + math::VEC3 emitterPos; + emitterPos.x = 0.0f; + emitterPos.y = 0.0f; + emitterPos.z = 0.0f; + math::VEC3Transform(&emitterPos, &mtxEmToGlb, &emitterPos); + + math::VEC3Sub(&particlePos, &particlePos, &emitterPos); + } + + math::VEC3 affect; + math::VEC3Sub(&affect, &infoCopy.translate, &particlePos); + + f32 lenSq = math::VEC3LenSq(&affect); + if (affect.x != 0.0f || affect.y != 0.0f || affect.z != 0.0f) { + math::VEC3Normalize(&affect, &affect); + } + math::VEC3Scale(&affect, &affect, infoCopy.power); + f32 distSq = infoCopy.refDistance * infoCopy.refDistance; + if (lenSq > distSq) { + math::VEC3Scale(&affect, &affect, distSq / lenSq); + } + if (infoCopy.emitterSpace) { + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + } else { + math::VEC3Transform(&affect, &mtxGlbToLocNoTrans, &affect); + } + + switch (infoCopy.addTarget) { + case 0 /* magic number (position) */: { + addVel += affect; + break; + } + case 1 /* magic number (velocity) */: { + addPos += affect; + break; + } + } break; } - case AC_TARGET_UNK36: {//Vortex? - // 8001a904 - struct UNKSTRUCT_UNK36 { - f32 FLOAT_0x0; - f32 FLOAT_0x4; - f32 FLOAT_0x8; - math::VEC3 VECTOR_0x8; - u8 BYTE_0x14; - u8 BYTE_0x15; - u8 BYTE_0x16; - u8 BYTE_0x17; - } infoCopy; - u8 *r8 = (pPtclTrack + pTrackAsHeader->keyTable+pTrackAsHeader->rangeTable+pTrackAsHeader->randomTable+pTrackAsHeader->nameTable); - infoCopy = *(UNKSTRUCT_UNK36*)(r8 + 0x20); - AnimCurveExecuteF32(pPtclTrack, &infoCopy.FLOAT_0x0, tick, seed, life); - /**/ - math::VEC3 stack_0x54; - math::VEC3 stack_0x9c; - math::VEC3 stack_0x90; - math::VEC3 stack_0x84; - Rotation2VecY(infoCopy.VECTOR_0x8, &stack_0x54); - if (infoCopy.BYTE_0x14) { - math::VEC3Transform(&stack_0x9c,&mtxGlbToLoc,&prevPos); - } else { - math::VEC3Transform(&stack_0x9c,&mtxLocToGlb,&prevPos); - stack_0x90.x = 0.0f; - stack_0x90.y = 0.0f; - stack_0x90.z = 0.0f; - math::VEC3Transform(&stack_0x90,&mtxLocToGlb,&stack_0x90); - math::VEC3Sub(&stack_0x9c,&stack_0x9c,&stack_0x90); - } - stack_0x9c *= math::VEC3Dot(&stack_0x9c,&stack_0x90); - infoCopy.FLOAT_0x8 *= infoCopy.FLOAT_0x8; - math::VEC3Sub(&stack_0x84,&infoCopy.VECTOR_0x8,&stack_0x9c); - f32 f20; - f32 f1 = math::VEC3Dot(&stack_0x84,&stack_0x84); - if (0.0f == f1) { - break; - } - if (f1 >= infoCopy.FLOAT_0x8) { - f20 = infoCopy.FLOAT_0x4; - } else { - f32 f3 = f1 / infoCopy.FLOAT_0x8; - f20 = (1.0f/*?*/ - f3) * infoCopy.FLOAT_0x4 + f3 * infoCopy.FLOAT_0x0; - } - math::VEC3Normalize(&stack_0x84,&stack_0x84); - math::VEC3Cross(&stack_0x84,&stack_0x84,&stack_0x54); - stack_0x84 *= f20; - if (infoCopy.BYTE_0x14) { - math::VEC3Transform(&stack_0x84,&mtxGlbToLoc,&stack_0x84); - } else { - math::VEC3Transform(&stack_0x84,&mtxLocToGlb,&stack_0x84); - } - switch (infoCopy.BYTE_0x15) { - case 0: { - addVel += stack_0x84; - break; - } - case 1: { - addPos += stack_0x84; - break; - } - } - /**/ + case AC_TARGET_FIELD_VORTEX: { + struct UNKSTRUCT_UNK36 { + f32 innerSpeed; // at 0x0 + f32 outerSpeed; // at 0x4 + f32 distance; // at 0x8 + math::VEC3 rotate; // at 0xC + bool emitterSpace; // at 0x14 + u8 addTarget; // at 0x15 + char UNK_0x16[2]; + } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + + AnimCurveExecuteF32(pPtclTrack, &infoCopy.innerSpeed, tick, seed, life); + + math::VEC3 forceDir; + Rotation2VecY(infoCopy.rotate, &forceDir); + + math::VEC3 particlePos; + if (infoCopy.emitterSpace) { + math::VEC3Transform(&particlePos, &mtxLocToEm, &prevPos); + } else { + math::VEC3Transform(&particlePos, &mtxLocToGlb, &prevPos); + + math::VEC3 emitterPos; + emitterPos.x = 0.0f; + emitterPos.y = 0.0f; + emitterPos.z = 0.0f; + math::VEC3Transform(&emitterPos, &mtxEmToGlb, &emitterPos); + + math::VEC3Sub(&particlePos, &particlePos, &emitterPos); + } + infoCopy.distance *= infoCopy.distance; + + math::VEC3 force; + math::VEC3Scale(&force, &forceDir, math::VEC3Dot(&forceDir, &particlePos)); + math::VEC3Sub(&force, &particlePos, &force); + + f32 lenSq = math::VEC3LenSq(&force); + if (0.0f == lenSq) { + break; + } + + f32 power; + if (lenSq >= infoCopy.distance) { + power = infoCopy.outerSpeed; + } else { + power = lenSq / infoCopy.distance; + power = (1.0f - power) * infoCopy.innerSpeed + power * infoCopy.outerSpeed; + } + + math::VEC3Normalize(&force, &force); + + math::VEC3 affect; + math::VEC3Cross(&affect, &force, &forceDir); + math::VEC3Scale(&affect, &affect, power); + + if (infoCopy.emitterSpace) { + math::VEC3Transform(&affect, &mtxEmToLocNoTrans, &affect); + } else { + math::VEC3Transform(&affect, &mtxGlbToLocNoTrans, &affect); + } + + switch (infoCopy.addTarget) { + case 0: { + addVel += affect; + break; + } + case 1: { + addPos += affect; + break; + } + } break; } } @@ -735,12 +763,13 @@ void ParticleManager::Calc() { } case AC_TYPE_PARTICLE_ROTATE: { - if (kind == AC_TARGET_12) { + switch (kind) { + case AC_TARGET_ROTATE: { + f32* pTarget = reinterpret_cast(&pIt->mParameter.mRotate); + AnimCurveExecuteRotate(pPtclTrack, pTarget, tick, seed, life); break; } - - f32* pTarget = reinterpret_cast(&pIt->mParameter.mRotate); - AnimCurveExecuteRotate(pPtclTrack, pTarget, tick, seed, life); + } break; } @@ -758,8 +787,6 @@ void ParticleManager::Calc() { break; } } - -#undef pTrackAsHeader } pIt->mTick++; From 0fc29e5189b4461b8840a575fdb49958e9e66a3b Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:27:27 +0300 Subject: [PATCH 7/9] Named `InfoFieldVortex` --- src/nw4r/ef/ef_particlemanager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nw4r/ef/ef_particlemanager.cpp b/src/nw4r/ef/ef_particlemanager.cpp index 0af2d3486..811f77199 100644 --- a/src/nw4r/ef/ef_particlemanager.cpp +++ b/src/nw4r/ef/ef_particlemanager.cpp @@ -686,7 +686,7 @@ void ParticleManager::Calc() { } case AC_TARGET_FIELD_VORTEX: { - struct UNKSTRUCT_UNK36 { + struct InfoFieldVortex { f32 innerSpeed; // at 0x0 f32 outerSpeed; // at 0x4 f32 distance; // at 0x8 @@ -694,7 +694,7 @@ void ParticleManager::Calc() { bool emitterSpace; // at 0x14 u8 addTarget; // at 0x15 char UNK_0x16[2]; - } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); + } infoCopy = *reinterpret_cast(pPtclTrack + sizeof(AnimCurveHeader) + pTrackAsHeader->keyTable + pTrackAsHeader->rangeTable + pTrackAsHeader->randomTable + pTrackAsHeader->nameTable); AnimCurveExecuteF32(pPtclTrack, &infoCopy.innerSpeed, tick, seed, life); From b4ddb029445e81568cb27e54e4623c586073c308 Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:35:10 +0300 Subject: [PATCH 8/9] Correct `configure.py` --- configure.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.py b/configure.py index 42e1e0031..8f55d058f 100755 --- a/configure.py +++ b/configure.py @@ -502,7 +502,7 @@ def MatchingFor(*versions): Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_emitter.cpp"), Object(NonMatching, "nw4r/ef/ef_animcurve.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_particle.cpp"), - Object(Matching, "nw4r/ef/ef_particlemanager.cpp"), + Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_particlemanager.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/ef_resource.cpp"), Object(Matching, "nw4r/ef/ef_util.cpp"), Object(Matching, "nw4r/ef/ef_handle.cpp"), @@ -518,7 +518,7 @@ def MatchingFor(*versions): Object(Matching, "nw4r/ef/emform/ef_cube.cpp"), Object(Matching, "nw4r/ef/drawstrategy/ef_drawstrategybuilder.cpp"), Object(not MatchingFor("RSPE01_00"), "nw4r/ef/drawstrategy/ef_drawstrategyimpl.cpp"), - Object(Matching, "nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp"), + Object(not MatchingFor("RSPE01_00"), "nw4r/ef/drawstrategy/ef_drawbillboardstrategy.cpp"), Object(NonMatching, "nw4r/ef/drawstrategy/ef_drawdirectionalstrategy.cpp"), Object(NonMatching, "nw4r/ef/drawstrategy/ef_drawfreestrategy.cpp"), Object(Matching, "nw4r/ef/drawstrategy/ef_drawlinestrategy.cpp"), From a807c2c27f65718458f34d436f66769c92d307ce Mon Sep 17 00:00:00 2001 From: GibHaltmannKill <67860669+GibHaltmannKill@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:18:20 +0300 Subject: [PATCH 9/9] Made progress on `nw4r::ef::AnimCurveExecuteU8` --- include/MSL/internal/math_double.h | 4 +- src/nw4r/ef/ef_animcurve.cpp | 417 +++++++++++++++++++++++++++++ 2 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 src/nw4r/ef/ef_animcurve.cpp diff --git a/include/MSL/internal/math_double.h b/include/MSL/internal/math_double.h index 99c886bc1..0d9f3721d 100644 --- a/include/MSL/internal/math_double.h +++ b/include/MSL/internal/math_double.h @@ -52,7 +52,9 @@ inline float modff(float x, float* iptr) { return frac; } -float fabsf(float x); +inline float fabsf(float x) { + return __fabs(x); +} #ifdef __cplusplus } diff --git a/src/nw4r/ef/ef_animcurve.cpp b/src/nw4r/ef/ef_animcurve.cpp new file mode 100644 index 000000000..e06881024 --- /dev/null +++ b/src/nw4r/ef/ef_animcurve.cpp @@ -0,0 +1,417 @@ +#include +#include +#include + +#include + +namespace nw4r { +namespace ef { + +inline void getAnimHeaders(u8* header, AnimCurveHeader** pHeader, AnimCurveKey** pKey, AnimCurveRandom** pRandom, AnimCurveRandomTable** pRandomTable, AnimCurveNameTable** pNameTable) { + *pHeader = reinterpret_cast(header); + *pKey = reinterpret_cast(reinterpret_cast(*pHeader) + sizeof(AnimCurveHeader)); + *pRandom = reinterpret_cast(reinterpret_cast(*pKey) + (*pHeader)->keyTable); + *pRandomTable = reinterpret_cast(reinterpret_cast(*pRandom) + (*pHeader)->rangeTable); + *pNameTable = reinterpret_cast(reinterpret_cast(*pRandomTable) + (*pHeader)->randomTable); +} + +inline void calcTick(u32 life, const AnimCurveHeader* pHeader, u32 origTick, u16* pTick, f32* pTickF32, u32* pDiv) { + *pDiv = 0; + + if (pHeader->frameLength <= 1) { + *pTick = 0; + *pTickF32 = 0.0f; + *pDiv = origTick; + return; + } + + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_INFLOOP) && pHeader->loopCount <= 1) { + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_FITTING)) { + *pTick = origTick >= pHeader->frameLength - 1 ? static_cast(pHeader->frameLength - 1) : static_cast(origTick); + *pTickF32 = *pTick; + return; + } + *pTickF32 = origTick; + *pTickF32 *= static_cast(pHeader->frameLength - 1) / (life - 1); + if (*pTickF32 > pHeader->frameLength - 1) { + *pTickF32 = pHeader->frameLength - 1; + } + *pTick = *pTickF32; + } else { + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_FITTING)) { + u32 div = origTick / (pHeader->frameLength - 1); + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_TURN)) { + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_INFLOOP) && div >= pHeader->loopCount) { + *pTick = pHeader->frameLength - 1; + *pDiv = static_cast(pHeader->loopCount - 1); + } else { + *pTick = origTick - div * (pHeader->frameLength - 1); + *pDiv = div; + } + } else { + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_INFLOOP) && div >= pHeader->loopCount) { + *pTick = (pHeader->loopCount % 2 == 0) ? static_cast(0) : static_cast(pHeader->frameLength - 1); + *pDiv = static_cast(pHeader->loopCount - 1); + } else if (div % 2 == 0) { + *pTick = origTick - div * (pHeader->frameLength - 1); + *pDiv = div; + } else { + *pTick = (pHeader->frameLength - 1) * (div + 1) - origTick; + *pDiv = div; + } + } + *pTickF32 = *pTick; + } else if (origTick >= life - 1) { + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_TURN) || pHeader->loopCount % 2 != 0) { + *pTick = origTick - 1; + } else { + *pTick = 0; + } + *pDiv = static_cast(pHeader->loopCount - 1); + *pTickF32 = *pTick; + } else { + f32 rate = pHeader->loopCount * (static_cast(pHeader->frameLength - 1) / (life - 1)); + u32 div = origTick * rate / (pHeader->frameLength - 1); + *pTick = div * (pHeader->frameLength - 1); + *pDiv = div; + f32 pos = origTick * rate - div * (pHeader->frameLength - 1); + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_TURN)) { + *pTickF32 = pos; + } else if (div % 2 == 0) { + *pTickF32 = pos; + } else { + *pTickF32 = (pHeader->frameLength - 1) - pos; + } + *pTick = *pTickF32; + } + } +} + +inline void searchKey(int& rFrameIdx, bool& rOnKey, u16& rLoFrame, u16& rHiFrame, int tick, f32 tickF32, u8* ptrBase, int framesize, int start, int end) { + int lo = start; + int hi = end; + int md = (start + end) / 2; + bool isInt = std::fabsf(tick - tickF32) < NW4R_MATH_FLT_EPSILON; + + rLoFrame = *reinterpret_cast(ptrBase + lo * framesize); + if (tick < rLoFrame) { + rFrameIdx = lo; + rOnKey = true; + return; + } + if (tick == rLoFrame) { + rFrameIdx = lo; + if (lo == end) { + rOnKey = true; + } else { + rOnKey = isInt; + if (!rOnKey) { + rHiFrame = *reinterpret_cast(ptrBase + (lo + 1) * framesize); + } + } + return; + } + rHiFrame = *reinterpret_cast(ptrBase + hi * framesize); + if (rHiFrame <= tick) { + rFrameIdx = hi; + rOnKey = true; + return; + } + u16 mdFrame = *reinterpret_cast(ptrBase + md * framesize); + while (lo < md) { + if (tick == mdFrame) { + rFrameIdx = md; + rOnKey = isInt; + if (!rOnKey) { + rLoFrame = mdFrame; + rHiFrame = *reinterpret_cast(ptrBase + (rFrameIdx + 1) * framesize); + } + return; + } + if (mdFrame < tick) { + lo = md; + rLoFrame = mdFrame; + } else { + hi = md; + rHiFrame = mdFrame; + } + md = (lo + hi) / 2; + mdFrame = *reinterpret_cast(ptrBase + md * framesize); + } + rFrameIdx = lo; + rOnKey = false; +} + +inline void CalcTurnDiv(const AnimCurveHeader* pHeader, const AnimCurveKey* pKey, int frameIdx, u32* pDivL, u32* pDivH) { + *pDivH = *pDivL; + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_TURN)) { + return; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_INFLOOP) && pHeader->loopCount <= 1) { + return; + } + if (*pDivL % 2 == 0 && frameIdx + 1 >= pKey->count - 1 && ((pHeader->processFlag & AnimCurveHeader::PROC_FLAG_INFLOOP) || pHeader->loopCount < *pDivL - 1)) { + ++*pDivH; + } + if (*pDivL % 2 != 0 && frameIdx == 0 && *pDivL > 0 /* redundant since every unsigned odd integer is positive */) { + ++*pDivL; + } +} + +inline u8 AnimCurveInterpHermite(u32 posF, u8 valL, u8 valH, u8 type) { + switch (type & 0x3 /* magic number */) { + case 0 /* magic number (linear) */: { + return valL + (posF * (valH - valL) >> 0x10); + } + case 1 /* magic number (Hermite) */: { + f32 p = posF; + p = p / 0x10000; + if (type == 1 /* magic number */) { + return valL + (valH - valL) * (3.0f - 2.0f * p) * p * p; + } + f32 p1 = !((type >> 2 /* magic number (derivative at valH) */) & 1) ? 0.0f : 1.5f; + f32 p2 = !((type >> 3 /* magic number (derivative at valL) */) & 1) ? 0.0f : 1.5f; + f32 result = valL + (valH - valL) * p * ((p2 + p1 - 2.0f) * p * p + (-2.0f * p2 - p1 + 3.0f) * p + p2); + if (result < 0) { + return 0; + } + if (result > 0xFF) { + return 0xFF; + } + return result; + } + case 2 /* magic number (piecewise constant/last keyframe?) */: { + return valL; + } + } + return 0; +} + +void AnimCurveExecuteU8(u8* pCmdList, u8* pTarget, u32 origTick, u16 seed, + u32 life) { + u8 enableFlag = pCmdList[3]; + u32 i; + u8 mask; + + for (i = 8, mask = 0x80; mask; mask >>= 1, i--) { + if (enableFlag & mask) { + break; + } + } + if (i == 0) { + return; + } + + int framesize = (i + 0xC + 1) & ~0x1; + + AnimCurveHeader* pHeader; + AnimCurveKey* pKey; + AnimCurveRandom* pRandom; + AnimCurveRandomTable* pRandomTable; + AnimCurveNameTable* pNameTable; + getAnimHeaders(pCmdList, &pHeader, &pKey, &pRandom, &pRandomTable, &pNameTable); + + const int RNDRANGE_SIZE = i * 2; + const int RNDSET_SIZE = i * 2; + + f32 tickF32; + u16 tick; + u32 divL; + calcTick(life, pHeader, origTick, &tick, &tickF32, &divL); + u32 divH = divL; + u8* ptrBase = pKey->datas; + int frameIdx; + bool onKey; + u16 loFrame; + u16 hiFrame; + searchKey(frameIdx, onKey, loFrame, hiFrame, tick, tickF32, ptrBase, framesize, 0, pKey->count - 1); + + if (onKey) { + u8* ptrFixed = ptrBase + frameIdx * framesize + 0xC; + if (!*(ptrFixed - 0xC + 0x2)) { + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + *pTarget = *ptrFixed++; + } + } + } else { + u16 randIdx = *reinterpret_cast(ptrFixed); + u32 rand = RANDOM_INIT(seed, pHeader->randomSeed, randIdx, divL); + u8* tbl; + int val; + if (!(*(ptrFixed - 0xC + 0x2) & 2)) { + tbl = pKey->datas + randIdx * RNDRANGE_SIZE; + } else { + tbl = pRandomTable->datas + (rand >> 0x10) % pRandomTable->count * RNDSET_SIZE; + rand = RANDOM_NEXT(rand); + } + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + val = *tbl++; + val += *tbl++ * (s16)(rand >> 0x10) / 0x8000; + if (val < 0) { + val = 0; + } + if (val > 0xFF) { + val = 0xFF; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_SYNC)) { + rand = RANDOM_NEXT(rand); + } + *pTarget = val; + } + } + } + } else { + CalcTurnDiv(pHeader, pKey, frameIdx, &divL, &divH); + u32 posF = (tickF32 - loFrame) * 0x10000; + posF /= hiFrame - loFrame; + u8* ptrL_ = ptrBase + frameIdx * framesize; + u8* ptrL = ptrBase + frameIdx * framesize + 0xC; + u8* ptrH = ptrBase + (frameIdx + 1) * framesize + 0xC; + u8* pType = ptrBase + frameIdx * framesize + 0x4; + bool isRandL = *(ptrL - 0xC + 0x2); + bool isRandH = *(ptrH - 0xC + 0x2); + if (!isRandL && !isRandH) { + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + if (*ptrL == *ptrH) { + *pTarget = *ptrL; + } else { + *pTarget = AnimCurveInterpHermite(posF, *ptrL, *ptrH, *pType); + } + ptrL++; + ptrH++; + } + pType++; + } + } else if (isRandL && !isRandH) { + u16 randIdx = *reinterpret_cast(ptrL); + u32 rand = RANDOM_INIT(seed, pHeader->randomSeed, randIdx, divL); + u8* tbl; + int val; + if (!(*(ptrL - 0xC + 0x2) & 2)) { + tbl = pKey->datas + randIdx * RNDRANGE_SIZE; + } else { + tbl = pRandomTable->datas + (rand >> 0x10) % pRandomTable->count * RNDSET_SIZE; + rand = RANDOM_NEXT(rand); + } + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + val = *tbl++; + val += *tbl++ * (s16)(rand >> 0x10) / 0x8000; + if (val < 0) { + val = 0; + } + if (val > 0xFF) { + val = 0xFF; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_SYNC)) { + rand = RANDOM_NEXT(rand); + } + *pTarget = AnimCurveInterpHermite(posF, val, *ptrH, *pType); + ptrL++; + ptrH++; + } + pType++; + } + } else if (!isRandL && isRandH) { + u16 randIdx = *reinterpret_cast(ptrH); + u32 rand = RANDOM_INIT(seed, pHeader->randomSeed, randIdx, divH); + u8* tbl; + int val; + if (!(*(ptrH - 0xC + 0x2) & 2)) { + tbl = pKey->datas + randIdx * RNDRANGE_SIZE; + } else { + tbl = pRandomTable->datas + (rand >> 0x10) % pRandomTable->count * RNDSET_SIZE; + rand = RANDOM_NEXT(rand); + } + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + val = *tbl++; + val += *tbl++ * (s16)(rand >> 0x10) / 0x8000; + if (val < 0) { + val = 0; + } + if (val > 0xFF) { + val = 0xFF; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_SYNC)) { + rand = RANDOM_NEXT(rand); + } + *pTarget = AnimCurveInterpHermite(posF, *ptrL, val, *pType); + ptrL++; + ptrH++; + } + pType++; + } + } else { + u16 randIdx; + randIdx = *reinterpret_cast(ptrL); + u32 randL = RANDOM_INIT(seed, pHeader->randomSeed, randIdx, divL); + u8* tblL; + if (!(*(ptrL - 0xC + 0x2) & 2)) { + tblL = pKey->datas + randIdx * RNDRANGE_SIZE; + } else { + tblL = pRandomTable->datas + (randL >> 0x10) % pRandomTable->count * RNDSET_SIZE; + randL = RANDOM_NEXT(randL); + } + randIdx = *reinterpret_cast(ptrH); + u32 randH = RANDOM_INIT(seed, pHeader->randomSeed, randIdx, divH); + u8* tblH; + if (!(*(ptrH - 0xC + 0x2) & 2)) { + tblH = pKey->datas + randIdx * RNDRANGE_SIZE; + } else { + tblH = pRandomTable->datas + (randH >> 0x10) % pRandomTable->count * RNDSET_SIZE; + randH = RANDOM_NEXT(randH); + } + int valL; + int valH; + for (; mask; pTarget++, mask >>= 1) { + if (enableFlag & mask) { + valL = *tblL++; + valL += *tblL++ * (s16)(randL >> 0x10) / 0x8000; + if (valL < 0) { + valL = 0; + } + if (valL > 0xFF) { + valL = 0xFF; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_SYNC)) { + randL = RANDOM_NEXT(randL); + } + valH = *tblH++; + valH += *tblH++ * (s16)(randH >> 0x10) / 0x8000; + if (valH < 0) { + valH = 0; + } + if (valH > 0xFF) { + valH = 0xFF; + } + if (!(pHeader->processFlag & AnimCurveHeader::PROC_FLAG_SYNC)) { + randH = RANDOM_NEXT(randH); + } + *pTarget = AnimCurveInterpHermite(posF, valL, valH, *pType); + ptrL++; + ptrH++; + } + pType++; + } + } + } +} + +void AnimCurveExecuteF32(u8* pCmdList, f32* pTarget, u32 tick, u16 seed, + u32 life); + +void AnimCurveExecuteRotate(u8* pCmdList, f32* pTarget, u32 tick, u16 seed, + u32 life); + +void AnimCurveExecuteTexture(u8* pCmdList, Particle* pParticle, u32 tick, + u16 seed, u32 life); + +void AnimCurveExecuteChild(u8* pCmdList, Particle* pParticle, u32 tick, + u16 seed, u32 life); + +} // namespace ef +} // namespace nw4r