#include "CDamageableTriggerExtra.h" #include "Core/Render/CDrawUtil.h" #include "Core/Render/CRenderer.h" #include #include CDamageableTriggerExtra::CDamageableTriggerExtra(CScriptObject *pInstance, CScene *pScene, CScriptNode *pParent) : CScriptExtra(pInstance, pScene, pParent) , mpMat(nullptr) { for (uint32 iTex = 0; iTex < 3; iTex++) mpTextures[iTex] = nullptr; SetInheritance(true, false, false); CreateMaterial(); CStructProperty* pProperties = pInstance->Template()->Properties(); // Fetch render side mRenderSide = TEnumRef(pInstance->PropertyData(), pProperties->ChildByIndex(5)); if (mRenderSide.IsValid()) PropertyModified(mRenderSide.Property()); // Fetch scale mPlaneSize = CVectorRef(pInstance->PropertyData(), pProperties->ChildByIndex(2)); if (mPlaneSize.IsValid()) PropertyModified(mPlaneSize.Property()); // Fetch textures for (uint32 TextureIdx = 0; TextureIdx < 3; TextureIdx++) { mTextureAssets[TextureIdx] = CAssetRef(pInstance->PropertyData(), pProperties->ChildByIndex(6 + TextureIdx)); if (mTextureAssets[TextureIdx].IsValid()) PropertyModified(mTextureAssets[TextureIdx].Property()); } } CDamageableTriggerExtra::~CDamageableTriggerExtra() { delete mpMat; } void CDamageableTriggerExtra::CreateMaterial() { ASSERT(!mpMat); mpMat = new CMaterial(mGame, EVertexAttribute::Position | EVertexAttribute::Normal | EVertexAttribute::Tex0); // Most values/TEV setup were found from the executable + from graphics debuggers // Animation parameters are estimates from eyeballing the values ingame mpMat->SetBlendMode(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); mpMat->SetLightingEnabled(true); mpMat->SetOptions(EMaterialOption::Transparent); mpMat->SetKonst(CColor((float) 1.f, 1.f, 1.f, 0.2f), 0); mpMat->SetNumPasses(3); CMaterialPass *pPassA = mpMat->Pass(0); pPassA->SetKColorSel(kKonst0_RGB); pPassA->SetTexCoordSource(4); pPassA->SetTexture(mpTextures[0]); pPassA->SetColorInputs(kZeroRGB, kTextureRGB, kKonstRGB, kZeroRGB); pPassA->SetAnimMode(EUVAnimMode::UVScroll); pPassA->SetAnimParam(3, -0.48f); CMaterialPass *pPassB = mpMat->Pass(1); pPassB->SetTexCoordSource(4); pPassB->SetTexture(mpTextures[1]); pPassB->SetColorInputs(kZeroRGB, kTextureRGB, kPrevRGB, kZeroRGB); pPassB->SetAnimMode(EUVAnimMode::UVScroll); pPassB->SetAnimParam(2, 0.25f); pPassB->SetAnimParam(3, -0.3f); CMaterialPass *pPassC = mpMat->Pass(2); pPassC->SetTexCoordSource(4); pPassC->SetTexture(mpTextures[2]); pPassC->SetRasSel(kRasColor0A0); pPassC->SetKAlphaSel(kKonst0_A); pPassC->SetColorInputs(kZeroRGB, kTextureRGB, kOneRGB, kPrevRGB); pPassC->SetAlphaInputs(kZeroAlpha, kZeroAlpha, kZeroAlpha, kKonstAlpha); pPassC->SetAnimMode(EUVAnimMode::UVScroll); pPassC->SetAnimParam(3, -0.16f); } void CDamageableTriggerExtra::UpdatePlaneTransform() { CVector3f Extent = mPlaneSize.Get() / 2.f; switch (mRenderSide) { case ERenderSide::North: case ERenderSide::South: { float Scalar = (mRenderSide == ERenderSide::North ? 1.f : -1.f); mPosition = CVector3f(0.f, Extent.Y * Scalar, 0.f); mRotation = CQuaternion::FromEuler(CVector3f(90.f * Scalar, 0.f, 0.f)); mScale = CVector3f(Extent.X, Extent.Z, 0.f); mCoordScale = mPlaneSize.Get().XZ(); break; } case ERenderSide::West: case ERenderSide::East: { float Scalar = (mRenderSide == ERenderSide::West ? 1.f : -1.f); mPosition = CVector3f(-Extent.X * Scalar, 0.f, 0.f); mRotation = CQuaternion::FromEuler(CVector3f(0.f, 90.f * Scalar, 0.f)); mScale = CVector3f(Extent.Z, Extent.Y, 0.f); mCoordScale = -mPlaneSize.Get().YZ(); break; } case ERenderSide::Up: case ERenderSide::Down: { float Scalar = (mRenderSide == ERenderSide::Up ? 1.f : -1.f); float RotAngle = (mRenderSide == ERenderSide::Up ? 180.f : 0.f); mPosition = CVector3f(0.f, 0.f, Extent.Z * Scalar); mRotation = CQuaternion::FromEuler(CVector3f(0.f, RotAngle, 0.f)); mScale = CVector3f(Extent.X, Extent.Y, 0.f); mCoordScale = -mPlaneSize.Get().XY(); break; } default: break; } if (mRenderSide == ERenderSide::NoRender) mLocalAABox = CAABox::skZero; else mLocalAABox = CAABox(CVector3f(-1.f, -1.f, 0.f), CVector3f(1.f, 1.f, 0.f)); MarkTransformChanged(); } CDamageableTriggerExtra::ERenderSide CDamageableTriggerExtra::RenderSideForDirection(const CVector3f& rkDir) { // Get the index of the largest XYZ component CVector3f AbsDir(Math::Abs(rkDir.X), Math::Abs(rkDir.Y), Math::Abs(rkDir.Z)); uint32 Max = (AbsDir.X > AbsDir.Y ? 0 : 1); Max = (AbsDir[Max] > AbsDir.Z ? Max : 2); // Check whether the direction is positive or negative. If the absolute value of the component matches the input one, then it's positive. bool Positive = (rkDir[Max] == AbsDir[Max]); // Return corresponding side for direction if (Max == 0) return (Positive ? ERenderSide::East : ERenderSide::West); else if (Max == 1) return (Positive ? ERenderSide::North : ERenderSide::South); else if (Max == 2) return (Positive ? ERenderSide::Up : ERenderSide::Down); return ERenderSide::NoRender; } CDamageableTriggerExtra::ERenderSide CDamageableTriggerExtra::TransformRenderSide(ERenderSide Side) { // DamageableTrigger has a convenience feature implemented that changes the // render side when the area's been rotated, so we need to replicate it here. CQuaternion AreaRotation = mpScriptNode->Instance()->Area()->Transform().ExtractRotation(); switch (Side) { case ERenderSide::North: return RenderSideForDirection(AreaRotation.YAxis()); case ERenderSide::South: return RenderSideForDirection(-AreaRotation.YAxis()); case ERenderSide::West: return RenderSideForDirection(-AreaRotation.XAxis()); case ERenderSide::East: return RenderSideForDirection(AreaRotation.XAxis()); case ERenderSide::Up: return RenderSideForDirection(AreaRotation.ZAxis()); case ERenderSide::Down: return RenderSideForDirection(-AreaRotation.ZAxis()); default: return ERenderSide::NoRender; } } void CDamageableTriggerExtra::OnTransformed() { UpdatePlaneTransform(); } void CDamageableTriggerExtra::PropertyModified(IProperty* pProperty) { if (pProperty == mRenderSide || pProperty == mPlaneSize) { UpdatePlaneTransform(); } else { for (uint32 TextureIdx = 0; TextureIdx < 3; TextureIdx++) { if (pProperty == mTextureAssets[TextureIdx].Property()) { mpTextures[TextureIdx] = gpResourceStore->LoadResource( mTextureAssets[TextureIdx].Get() ); if (mpTextures[TextureIdx] && mpTextures[TextureIdx]->Type() != EResourceType::Texture) mpTextures[TextureIdx] = nullptr; mpMat->Pass(TextureIdx)->SetTexture(mpTextures[TextureIdx]); break; } } } } bool CDamageableTriggerExtra::ShouldDrawNormalAssets() { return (mRenderSide == ERenderSide::NoRender); } void CDamageableTriggerExtra::AddToRenderer(CRenderer *pRenderer, const SViewInfo& rkViewInfo) { if (rkViewInfo.GameMode && !mpInstance->IsActive()) return; if (!rkViewInfo.GameMode && ((rkViewInfo.ShowFlags & EShowFlag::ObjectGeometry) == 0)) return; if (mRenderSide != ERenderSide::NoRender) { if (rkViewInfo.ViewFrustum.BoxInFrustum(AABox())) pRenderer->AddMesh(this, -1, AABox(), true, ERenderCommand::DrawMesh); if (mpParent->IsSelected() && !rkViewInfo.GameMode) pRenderer->AddMesh(this, -1, AABox(), false, ERenderCommand::DrawSelection); } } void CDamageableTriggerExtra::Draw(FRenderOptions Options, int /*ComponentIndex*/, ERenderCommand /*Command*/, const SViewInfo& rkViewInfo) { LoadModelMatrix(); CGraphics::sPixelBlock.TintColor = mpParent->TintColor(rkViewInfo); mpMat->SetCurrent(Options); // Note: The plane the game renders this onto is 5x4.5, which is why we divide the tex coords by this value CVector2f TexUL(0.f, mCoordScale.Y / 4.5f); CVector2f TexUR(mCoordScale.X / 5.f, mCoordScale.Y / 4.5f); CVector2f TexBR(mCoordScale.X / 5.f, 0.f); CVector2f TexBL(0.f, 0.f); CDrawUtil::DrawSquare(TexUL, TexUR, TexBR, TexBL); } void CDamageableTriggerExtra::DrawSelection() { glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); glBlendFunc(GL_ONE, GL_ZERO); LoadModelMatrix(); CDrawUtil::UseColorShader(WireframeColor()); CDrawUtil::DrawSquare(); glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); } void CDamageableTriggerExtra::RayAABoxIntersectTest(CRayCollisionTester& rTester, const SViewInfo& rkViewInfo) { if (mRenderSide == ERenderSide::NoRender) return; if (rkViewInfo.GameMode && !mpInstance->IsActive()) return; const CRay& rkRay = rTester.Ray(); if (rkViewInfo.pRenderer->RenderOptions() & ERenderOption::EnableBackfaceCull) { // We're guaranteed to be axis-aligned, so we can take advantage of that // to perform a very simple backface check. switch (mRenderSide) { case ERenderSide::North: if (rkRay.Origin().Y > AbsolutePosition().Y) return; break; case ERenderSide::South: if (rkRay.Origin().Y < AbsolutePosition().Y) return; break; case ERenderSide::West: if (rkRay.Origin().X < AbsolutePosition().X) return; break; case ERenderSide::East: if (rkRay.Origin().X > AbsolutePosition().X) return; break; case ERenderSide::Up: if (rkRay.Origin().Z > AbsolutePosition().Z) return; break; case ERenderSide::Down: if (rkRay.Origin().Z < AbsolutePosition().Z) return; break; default: break; } } std::pair Result = AABox().IntersectsRay(rkRay); if (Result.first) { rTester.AddNode(this, -1, Result.second); mCachedRayDistance = Result.second; } } SRayIntersection CDamageableTriggerExtra::RayNodeIntersectTest(const CRay& rkRay, uint32 /*ComponentIndex*/, const SViewInfo& /*rkViewInfo*/) { // The bounding box and all other tests already passed in RayAABoxIntersectTest, so we // already know that we have a positive. return SRayIntersection(true, mCachedRayDistance, rkRay.PointOnRay(mCachedRayDistance), mpParent, -1); }