#include "m/m3d/m_shadow.h" #include "c/c_math.h" #include "m/m_mtx.h" #include "m/m_quat.h" #include "m/m_vec.h" #include "nw4r/g3d/g3d_calcview.h" #include "nw4r/g3d/g3d_draw.h" #include "nw4r/g3d/g3d_draw1mat1shp.h" #include "nw4r/g3d/g3d_scnmdl.h" #include "nw4r/g3d/g3d_scnmdlsmpl.h" #include "nw4r/g3d/g3d_state.h" #include "nw4r/g3d/res/g3d_resmat.h" #include "nw4r/g3d/res/g3d_resmdl.h" #include "nw4r/g3d/res/g3d_resshp.h" // All of this is completely made up, as we don't have symbols for this TU // (contrary to the rest of m3d and most of nw4r::g3d) #define FRM_HEAP_STATE 'tag0' namespace m3d { mShadow_c *mShadow_c::sInstance; GXTexObj *mShadow_c::sTexObj; nw4r::g3d::ResShp mShadow_c::sResShp; mShadow_c::~mShadow_c() { remove(); } void mShadow_c::drawOpa() { if (field_0x66) { beforeDraw(); mShadowChild_c *child = static_cast(nw4r::ut::List_GetFirst(&mList)); while (child != nullptr) { child->draw(); child = static_cast(nw4r::ut::List_GetNext(&mList, child)); } afterDraw(); } } void mShadow_c::create( int count, u8 unk1, int unk2, u16 texSize, u32 drawOpaPriority, nw4r::g3d::ResMdl mdl, u32 heapSize ) { // Regswaps mAllocator.attach(mpHeap, 0x20); proc_c::create(&mAllocator, nullptr); u32 bufSize = GXGetTexBufferSize(texSize, texSize, GX_TF_RGB565, false, 0); mTexBufferSize = bufSize; u32 numTexBuffers = (unk2 + 2) / 3; mpTexBuf = mpHeap->alloc(bufSize * numTexBuffers, 0x20); // Maybe an Inline? EGG::FrmHeap **heap = mpFrmHeaps; for (int i = 0; i < 2; i++) { heap[0] = mHeap::createFrmHeap(heapSize, mpHeap, "ShadowTmp", 0x4, mHeap::OPT_NONE); heap[0]->recordState(FRM_HEAP_STATE); heap++; } swapHeaps(); mShadowChild_c *childs = new (mpHeap, 0x04) mShadowChild_c[count]; mpChilds = childs; for (int i = 0; i < count; i++) { childs->create(unk1, mpHeap); childs++; } setPriorityDraw(drawOpaPriority, 0); setOption(/* DISABLE_DRAW_XLU */ 0x07, 1); mChildCount = count; mFreeChildIdx = 0; mNumTexBuffers = numTexBuffers * 3; mFreeTexIdx = 0; mTexSize = texSize; nw4r::g3d::ResMat mat = mdl.GetResMat(0); sResShp = mdl.GetResShp(0); sTexObj = mat.GetResTexObj().GetTexObj(GX_TEXMAP0); } void mShadow_c::remove() { if (mpHeap != nullptr) { if (mpChilds != nullptr) { delete[] mpChilds; mpChilds = nullptr; } EGG::FrmHeap **heap = mpFrmHeaps; for (int i = 0; i < 2; i++) { if (heap[i] != nullptr) { mHeap::destroyFrmHeap(heap[i]); heap[i] = nullptr; } } if (mpTexBuf != nullptr) { mpHeap->free(mpTexBuf); mpTexBuf = nullptr; } scnLeaf_c::remove(); mpHeap = nullptr; } } void mShadow_c::reset() { mShadowChild_c *iter = static_cast(nw4r::ut::List_GetFirst(&mList)); while (iter != nullptr) { iter->mNumLeaves = 0; mShadowCircle_c *circle = iter->mpCircle; iter->mpCircle = nullptr; circle->mpChild = nullptr; iter = static_cast(nw4r::ut::List_GetNext(&mList, iter)); } mFreeChildIdx = 0; mFreeTexIdx = 0; nw4r::ut::List_Init(&mList, 0); swapHeaps(); } bool mShadow_c::addCircle(mShadowCircle_c *circle, u32 priority, u32 isMdl) { if (circle->mpChild != nullptr) { // Already linked, OK return true; } mShadowChild_c *childPtr; if (mFreeChildIdx < mChildCount && (!isMdl || mFreeTexIdx < mNumTexBuffers)) { // There are free entries in our buffer, so just add a new child childPtr = &mpChilds[mFreeChildIdx]; if (mFreeChildIdx++ == 0) { entry(); } if (isMdl) { mFreeTexIdx++; } } else { // There are no free entries in our buffer, so replace an existing entry? childPtr = static_cast(nw4r::ut::List_GetLast(&mList)); if (priority >= childPtr->mPriorityMaybe) { return false; } if (isMdl) { if (mFreeTexIdx >= mNumTexBuffers) { while (childPtr->mNumLeaves == 0) { childPtr = static_cast(nw4r::ut::List_GetPrev(&mList, childPtr)); if (priority >= childPtr->mPriorityMaybe) { return false; } } } else { mFreeTexIdx++; } } childPtr->mNumLeaves = 0; childPtr->mpCircle->mpChild = nullptr; nw4r::ut::List_Remove(&mList, childPtr); } circle->mpChild = childPtr; childPtr->mpCircle = circle; childPtr->mPriorityMaybe = priority; mShadowChild_c *iter = static_cast(nw4r::ut::List_GetFirst(&mList)); if (iter == nullptr) { nw4r::ut::List_Append(&mList, childPtr); return true; } else { do { if (childPtr->mPriorityMaybe < iter->mPriorityMaybe) { nw4r::ut::List_Insert(&mList, iter, childPtr); return true; } iter = static_cast(nw4r::ut::List_GetNext(&mList, iter)); } while (iter != nullptr); nw4r::ut::List_Append(&mList, childPtr); } return true; } bool mShadow_c::drawMdl( mShadowCircle_c *circle, u32 priority, scnLeaf_c &mdl, const mQuat_c &quat, mVec3_c &pos, mColor color, u32 param9, f32 dist ) { if (!addCircle(circle, priority, 1)) { return false; } mShadowChild_c *child = circle->mpChild; child->set(pos, dist, color); child->set0x154(param9); return child->addMdl(mdl, quat); } bool mShadow_c::addMdlToCircle(mShadowCircle_c *circle, scnLeaf_c &mdl, const mQuat_c &quat) { if (circle->mpChild == nullptr) { return false; } return circle->mpChild->addMdl(mdl, quat); } void mShadow_c::removeCircle(mShadowCircle_c *circle) { mShadowChild_c *child = circle->mpChild; if (child == nullptr) { return; } circle->mpChild = nullptr; child->mpCircle = nullptr; nw4r::ut::List_Remove(&mList, child); } static f32 sTexMtx[2][4] = { {1.0f, 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f, 0.0f} }; static u32 STEP = 0x2492; static GXColor sColors[] = { {0xFF, 0x00, 0x00, 0x00}, {0x00, 0xFF, 0x00, 0x00}, {0x00, 0x00, 0xFF, 0x00}, {0x00, 0x00, 0x00, 0x00}, }; bool mShadow_c::drawTexObj( mShadowCircle_c *circle, u32 priority, const GXTexObj *texObj, const mMtx_c &mtx, const mQuat_c &quat, mVec3_c &pos, mColor color, u32 param9, f32 dist ) { if (!addCircle(circle, priority, 0)) { return false; } mShadowChild_c *child = circle->mpChild; child->set(pos, dist, color); child->set0x154(param9); return child->setGeom(texObj, mtx, quat); } static void drawSub2(void *data, u8 i) { s16 ang; u32 wid = i * 2; GXSetTexCopySrc(0, 0, wid, wid); GXSetTexCopyDst(i, i, GX_TF_RGB565, 1); GXCopyTex(data, false); GXPixModeSync(); GXInvalidateTexAll(); GXTexObj obj; GXInitTexObj(&obj, data, i, i, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, 0); GXInitTexObjLOD(&obj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); GXLoadTexObj(&obj, GX_TEXMAP0); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBX8, 0); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0); GXSetNumChans(0); GXSetNumTexGens(8); GXSetNumTevStages(8); GXSetTevColor(GX_TEVREG1, nw4r::ut::Color(0, 0, 0, 0x20)); ang = 0; for (int id = GX_TEXCOORD0, idx = GX_TEXMTX0; id < GX_MAX_TEXCOORD; id += 1, idx += (GX_TEXMTX1 - GX_TEXMTX0), ang += STEP) { GXSetTexCoordGen2((GXTexCoordID)id, GX_TG_MTX2x4, GX_TG_TEX0, idx, FALSE, GX_DUALMTX_IDENT); sTexMtx[0][3] = 0.01f * nw4r::math::CosIdx(ang); sTexMtx[1][3] = 0.01f * nw4r::math::SinIdx(ang); GXLoadTexMtxImm(sTexMtx, idx, GX_MTX2x4); } GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A0, GX_CC_ZERO); for (int id = GX_TEVSTAGE1; id < GX_TEVSTAGE8; id++) { GXSetTevSwapMode((GXTevStageID)id, GX_TEV_SWAP0, GX_TEV_SWAP0); GXSetTevOrder((GXTevStageID)id, (GXTexCoordID)id, GX_TEXMAP0, GX_COLOR_NULL); GXSetTevColorIn((GXTevStageID)id, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, GX_CC_CPREV); GXSetTevColorOp((GXTevStageID)id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV); GXSetTevAlphaIn((GXTevStageID)id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); GXSetTevAlphaOp((GXTevStageID)id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV); } GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_SET); GXLoadPosMtxImm(mMtx_c::Identity, 0); GXSetCurrentMtx(0); GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition2u16(0, 0); GXPosition2u8(0, 0); GXPosition2u16(wid, 0); GXPosition2u8(1, 0); GXPosition2u16(wid, wid); GXPosition2u8(1, 1); GXPosition2u16(0, wid); GXPosition2u8(0, 1); // GXEnd(); } static void drawSub1(void *data, u8 i) { Mtx44 mtx; u16 wid = i * 2; f32 wid2 = i * 2; C_MTXOrtho(mtx, 0.0f, wid2, 0.0f, wid2, 0.0f, 1.0f); GXSetProjection(mtx, GX_ORTHOGRAPHIC); drawSub2(data, i); GXSetNumChans(1); GXSetNumTexGens(0); GXSetNumTevStages(1); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXBegin(GX_LINESTRIP, GX_VTXFMT0, 5); GXPosition2u16(0, 0); GXPosition2u16(wid, 0); GXPosition2u16(wid, wid); GXPosition2u16(0, wid); GXPosition2u16(0, 0); // GXEnd(); GXSetZMode(true, GX_ALWAYS, true); GXSetTexCopySrc(0, 0, wid, wid); GXSetTexCopyDst(i, i, GX_TF_RGB565, 1); GXCopyTex(data, true); GXPixModeSync(); GXInvalidateTexAll(); } void mShadow_c::drawAllShadows() { mShadowChild_c *iter = static_cast(nw4r::ut::List_GetFirst(&mList)); if (iter != nullptr) { f32 wid = mTexSize * 2; u16 wid2 = mTexSize * 2; GXSetViewport(0.0f, 0.0f, wid, wid, 0.0f, 1.0f); GXSetScissor(0, 0, wid2, wid2); GXSetScissorBoxOffset(0, 0); GXLoadTexObj(mShadow_c::sTexObj, GX_TEXMAP1); GXSetNumChans(1); GXSetChanCtrl(GX_COLOR0A0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE); GXSetNumTexGens(0); GXSetNumTevStages(1); GXSetNumIndStages(0); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO); GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV); GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO); GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV); GXSetZCompLoc(1); GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_SET); GXSetZMode(false, GX_ALWAYS, false); GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0); GXColor clr = {0}; GXSetFog(GX_FOG_NONE, clr, 0.0f, 0.0f, 0.0f, 0.0f); GXSetFogRangeAdj(0, 0, 0); GXSetCullMode(GX_CULL_BACK); GXSetDither(0); GXSetClipMode(GX_CLIP_DISABLE); GXLoadPosMtxImm(mMtx_c::Identity, 0); GXSetCurrentMtx(0); Mtx44 mtx; C_MTXOrtho(mtx, 0.0f, wid, 0.0f, wid, 0.0f, 1.0f); GXSetProjection(mtx, GX_ORTHOGRAPHIC); GXSetLineWidth(0x18, 0); int i = 0; void *imgData = mpTexBuf; do { iter->updateMtx(); if (iter->mNumLeaves != 0) { if (i == 0) { GXClearVtxDesc(); GXSetVtxDesc(GX_VA_POS, GX_DIRECT); GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_U16, 0); GXBegin(GX_QUADS, GX_VTXFMT0, 4); GXPosition2u16(0, 0); GXPosition2u16(wid2, 0); GXPosition2u16(wid2, wid2); GXPosition2u16(0, wid2); // GXEnd(); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); GXSetBlendMode(GX_BM_LOGIC, GX_BL_ONE, GX_BL_ONE, GX_LO_OR); } iter->mColorChanIdx = i; GXInitTexObj(&iter->mTexObj, imgData, mTexSize, mTexSize, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, false); GXInitTexObjLOD(&iter->mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1); iter->drawMdl(); i = (i + 1) % 3; if (i == 0) { drawSub1(imgData, mTexSize); imgData = (void *)((u8 *)imgData + mTexBufferSize); } } iter = static_cast(nw4r::ut::List_GetNext(&mList, iter)); } while (iter != nullptr); if (i != 0) { drawSub1(imgData, mTexSize); } GXSetClipMode(GX_CLIP_ENABLE); GXSetDither(true); nw4r::g3d::G3DState::Invalidate(0x7FF); } } void mShadow_c::create(const mShadowCircleConfig *config, nw4r::g3d::ResMdl mdl, EGG::Heap *heap) { mShadow_c::sInstance = new (heap, 0x04) mShadow_c(heap); mShadow_c::GetInstance()->create( config->count, config->unk1, config->unk2, config->texBufferSize, config->drawOpaPriority, mdl, config->heapSize ); } void mShadow_c::beforeDraw() { if (mpCallback != nullptr) { mpCallback->beforeDraw(); } } void mShadow_c::draw(const mMtx_c &mtx, u32 unk) { if (mpCallback != nullptr) { mpCallback->draw(mtx, unk); } } void mShadow_c::afterDraw() { if (mpCallback != nullptr) { mpCallback->afterDraw(); } } void mShadow_c::swapHeaps() { changeHeap(mCurrentHeapIdx + 1); mpCurrentHeap->freeByState(FRM_HEAP_STATE); mpCurrentHeap->recordState(FRM_HEAP_STATE); } void mShadow_c::destroy() { if (mShadow_c::sInstance != nullptr) { mShadow_c::GetInstance()->remove(); delete mShadow_c::sInstance; mShadow_c::sInstance = nullptr; } } mShadowChild_c::~mShadowChild_c() { if (mpCircle != nullptr) { mpCircle->mpChild = nullptr; mpCircle = nullptr; } if (mpLeaves != nullptr) { mpHeap->free(mpLeaves); mpLeaves = nullptr; } } bool mShadowChild_c::create(u8 maxNum, EGG::Heap *heap) { if ((mpLeaves = static_cast(heap->alloc(maxNum * sizeof(scnLeaf_c *), 0x04))) == nullptr) { return false; } mpHeap = heap; mMaxNumLeaves = maxNum; return true; } void mShadowChild_c::set(const mVec3_c &pos, f32 dist, mColor color) { mPositionMaybe = pos; mOffsetMaybe = dist; mShadowColor = color; } bool mShadowChild_c::addMdl(scnLeaf_c &mdl, const mQuat_c &quat) { if (!(mNumLeaves < mMaxNumLeaves)) { return false; } mMtx_c mtx; if (mdl.getType() == 0) { mdl.getLocalMtx(mtx); } else { mtx.copyFrom(static_cast(mdl).mMtx); } mQuat_c q = quat; mtx.applyQuat(q); if (mNumLeaves == 0) { mQuat = q; } else { mQuat.fn_802F2780(q); } mpLeaves[mNumLeaves++] = &mdl; return true; } bool mShadowChild_c::setGeom(const GXTexObj *texObj, const mMtx_c &mtx, const mQuat_c &quat) { mQuat = quat; MTXMultVec(mtx.m, mQuat.v, mQuat.v); if (texObj == nullptr) { mTexObj = *mShadow_c::sTexObj; } else { mTexObj = *texObj; } return true; } void mShadowChild_c::updateMtx() { // NONMATCHING field_0x13C = mQuat.w; mVec3_c a(mQuat.v.x, mQuat.v.y, mQuat.v.z); a += mPositionMaybe * GetOffset(); mVec3_c b(mQuat.v.x, mQuat.v.y, mQuat.v.z); b -= mPositionMaybe * field_0x13C; const mVec3_c *up; if (cM::isZero((a - b).squareMagXZ())) { up = &mVec3_c::Ez; } else { up = &mVec3_c::Ey; } mMtx_c mtx; C_MTXLookAt(mtx.m, a, *up, b); f32 f = field_0x13C; mFrustum.set(f, -f, -f, f, f, f + GetOffset(), mtx, true); } void mShadowChild_c::drawMdl() { using namespace nw4r; Mtx44 mtx; GXSetTevColor(GX_TEVREG0, sColors[mColorChanIdx]); C_MTXOrtho(mtx, field_0x13C, -field_0x13C, -field_0x13C, field_0x13C, 0.0f, 100.0f + (-mFrustum.mFar)); GXSetProjection(mtx, GX_ORTHOGRAPHIC); g3d::G3DState::Invalidate(0x7FF); mMtx_c &viewMtx = mFrustum.mView; for (scnLeaf_c **leaf = &mpLeaves[mNumLeaves - 1]; leaf >= &mpLeaves[0]; leaf--) { if ((*leaf)->getType() == 0 /* Model */) { g3d::ScnMdlSimple *mdl = g3d::G3dObj::DynamicCast((*leaf)->getG3dObject()); u32 bufSize = mdl->GetNumViewMtx() * sizeof(math::MTX34); math::MTX34 *viewPosArray = static_cast(mShadow_c::GetInstance()->mpCurrentHeap->alloc(bufSize, 0x20)); g3d::ResMdl resMdl = mdl->GetResMdl(); g3d::CalcView( viewPosArray, nullptr, mdl->GetWldMtxArray(), mdl->GetWldMtxAttribArray(), mdl->GetNumViewMtx(), viewMtx, resMdl, nullptr ); DCStoreRange(viewPosArray, bufSize); g3d::ScnMdl *mdl2 = g3d::G3dObj::DynamicCast((*leaf)->getG3dObject()); g3d::DrawResMdlReplacement *pRep = mdl2 ? &mdl2->GetDrawResMdlReplacement() : nullptr; g3d::DrawResMdlDirectly( resMdl, viewPosArray, nullptr, nullptr, mdl->GetByteCode(g3d::ScnMdlSimple::BYTE_CODE_DRAW_OPA), nullptr, pRep, g3d::RESMDL_DRAWMODE_FORCE_LIGHTOFF | g3d::RESMDL_DRAWMODE_IGNORE_MATERIAL ); GXInvalidateVtxCache(); } else { // this happens with bomb bag, and goes to 0x802EDC90 (mCustomShadow_c::draw) static_cast(*leaf)->draw(viewMtx); } } } static GXTevColorChan sChans[] = {GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA}; void mShadowChild_c::draw() { if (mNumLeaves != 0) { GXTevColorChan chan = sChans[mColorChanIdx]; GXSetTevSwapModeTable(GX_TEV_SWAP0, chan, chan, chan, chan); } else { GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); } GXLoadTexObj(&mTexObj, GX_TEXMAP0); GXSetTevColor(GX_TEVREG0, mShadowColor); Mtx mtx; C_MTXLightOrtho(mtx, field_0x13C, -field_0x13C, -field_0x13C, field_0x13C, 0.5f, -0.5f, 0.5f, 0.5f); MTXConcat(mtx, mFrustum.mView.m, mtx); GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4); mShadow_c::GetInstance()->draw(mFrustum.mView, field_0x154); GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA); } mShadowCircle_c::~mShadowCircle_c() { mShadow_c::GetInstance()->removeCircle(this); } mCustomShadow_c::~mCustomShadow_c() {} int mCustomShadow_c::getType() const { return 0x2; } void mCustomShadow_c::draw(const mMtx_c &arg) { nw4r::g3d::G3DState::Invalidate(0x7FF); GXSetNumTexGens(1); GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3C, false, 0x7D); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_C0, GX_CC_TEXC, GX_CC_ZERO); mMtx_c result; calc(arg, result); static nw4r::g3d::ResMat nullMat(nullptr); nw4r::g3d::Draw1Mat1ShpDirectly(nullMat, mShadow_c::sResShp, result, result, 0x18, nullptr, nullptr); GXSetNumTexGens(0); GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0); GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0); } void mCustomShadow_c::calc(mMtx_c mtx, mMtx_c &mtx2) const { mVec3_c trans; mtx2.copyFrom(mMtx); mVec3_c offset(0.0f, field_0x48, 0.0f); MTXMultVec(mtx2, offset, trans); MTXMultVec(mtx, trans, trans); MTXTrans(mtx2, trans.x, trans.y, trans.z); mMtx_c scaleMtx; MTXScale(scaleMtx, field_0x4C, field_0x4C, field_0x4C); MTXConcat(mtx2, scaleMtx, mtx2); } } // namespace m3d