using BrewMonster; using BrewMonster.Managers; using BrewMonster.Network; using BrewMonster.Scripts; using CSNetwork; using CSNetwork.GPDataType; using CSNetwork.Protocols; using CSNetwork.Protocols.rpcdata; using CSNetwork.Protocols.RPCData; using ModelRenderer.Scripts.Common; using System; using System.Data; using System.Text; using BrewMonster.PerfectWorld.Scripts.Vfx; using UnityEngine; using static CECNPC; namespace PerfectWorld.Scripts.Player { public class EC_ElsePlayer : CECPlayer { public int m_iAppearFlag; // Player's appearing flag public bool m_bBaseInfoReady; // true, Basic info is ready public bool m_bCustomReady; // true, Customized data is ready public bool m_bEquipReady; // true, Equipment data is ready A3DVECTOR3 m_vMoveDir; // Player's velocity public A3DVECTOR3 m_vServerPos; // Player's real position on server A3DVECTOR3 m_vStopDir; // The direction when player stop moving // 是否是依附者 = Is it a dependent/attacher? bool m_bHangerOn; // 依附者或被依附者id = The ID of the attacher (dependent) or the attached target. int m_iBuddyId; bool m_bStopMove; // Stop move flag public const float MAX_LAGDIST = 10.0f; // Maximum lag distance A3DVECTOR3 g_vAxisY = new A3DVECTOR3(0.0f, 1.0f, 0.0f); long m_dwLastMoveTime = 0; // Last move command arrived time float m_fMoveSpeed; // Move speed OtherPlayer_Move_Info m_cdr = new OtherPlayer_Move_Info(); float m_fDistToHost = 0f; // Distance to host player float m_fDistToHostH = 0f; // Horizontal distance to host player private BaseVfxObject m_pAppearGFX; // Appear GFX public CECCounter m_FightCnt; CECEPWorkMan m_pEPWorkMan; CECHostPlayer pHost => EC_ManMessageMono.Instance?.GetECManPlayer?.GetHostPlayer(); public void Init(info_player_1 Info, int iAppearFlag) { SetUpPlayer(); m_dwResFlags = (uint)PlayerResourcesReadyFlag.RESFG_ALL; m_pEPWorkMan = new CECEPWorkMan(this); CalcPlayerAABB(); SetPlayerInfor(new INFO(Info.cid, Info.crc_e, Info.crc_c)); SetServerPos(Info.pos); SetPos(Info.pos); m_cdr.fStepHeight = m_MoveConst.fStepHei; m_cdr.vExts = m_aabbServer.Extents; m_cdr.vVelocity.Clear(); m_FightCnt = new CECCounter(); m_FightCnt.SetPeriod(15000); m_FightCnt.Reset(true); A3DVECTOR3 vPos = GetPos(); m_aabb.Center = vPos + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = vPos + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); if (TryGetComponent(out var visual)) { visual.InitPlayerEventDoneHandler(); } LoadAppearGfx(); } public void MoveTo(cmd_object_move Cmd) { BrewMonster.BMLogger.Log("HoangDev : MoveToMoveTo"); if (Cmd.use_time == 0) return; SetServerPos(Cmd.dest); m_vMoveDir = Cmd.dest - GetPos(); float fDist = m_vMoveDir.Normalize(); m_bStopMove = false; // If destination position is too far to us, forcely pull player // to that position. if (fDist >= MAX_LAGDIST) { BrewMonster.BMLogger.Log("HoangDev : fDist >= MAX_LAGDIST"); SetPos(Cmd.dest); //m_pEPWorkMan.FinishWork(CECEPWork::WORK_MOVE); return; } int iMoveMode = Cmd.move_mode; m_cdr.bTraceGround = true; if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_AIR) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_AIR; m_cdr.bTraceGround = false; } else if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_WATER) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_WATER; m_cdr.bTraceGround = false; //ShowWing(false); } else { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_GROUND; //if (!IsFlying()) // ShowWing(false); } switch ((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_MASK) { case GPMoveMode.GP_MOVE_WALK: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = false; break; case GPMoveMode.GP_MOVE_RUN: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = true; break; case GPMoveMode.GP_MOVE_SLIDE: m_iMoveMode = (int)MoveMode.MOVE_SLIDE; break; case GPMoveMode.GP_MOVE_FALL: m_iMoveMode = (int)MoveMode.MOVE_FREEFALL; m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_FLYFALL: m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_JUMP: m_iMoveMode = (int)MoveMode.MOVE_JUMP; m_cdr.bTraceGround = false; break; } long dwTimeNow = Environment.TickCount; long dwDeltaTime = (dwTimeNow > m_dwLastMoveTime) ? (dwTimeNow - m_dwLastMoveTime) : 0; m_dwLastMoveTime = dwTimeNow; if (dwDeltaTime < 500) dwDeltaTime = 500; if (dwDeltaTime > 1000) dwDeltaTime = 1000; float fSpeed = (Cmd.sSpeed) / 256f; // short / 256 <=> FIX8TOFLOAT(short) m_fMoveSpeed = fDist / (dwDeltaTime * 0.001f); Mathf.Clamp(m_fMoveSpeed, 0.0f, fSpeed * 1.2f); //if (!m_pPlayerModel) return; //if (!IsValidAction(iCurAction)) return; PlayAction(GetMoveStandAction(true), true, 1, false); } public bool MovingTo(float dwDeltaTime) { bool bRet = false; A3DVECTOR3 vPos, vCurPos = GetPos(); float fDeltaTime = dwDeltaTime; if (m_bStopMove) { A3DVECTOR3 vDir = m_vServerPos - vCurPos; float fDist = vDir.Normalize(); if (vDir.IsZero()) return false; Quaternion targetRotation = Quaternion.LookRotation(EC_Utility.ToVector3(vDir)); if (Quaternion.Angle(transform.rotation, targetRotation) < 0.5f) transform.rotation = targetRotation; else transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, rotationSpeed * Time.deltaTime ); vPos = MoveStep(vDir, m_fMoveSpeed, fDeltaTime); float fMoveDelta = A3d_Magnitude(vPos - vCurPos); if (Math.Abs(fMoveDelta - 0f) <= float.Epsilon || fMoveDelta >= fDist) //!fMoveDelta <=> (Math.Abs(fMoveDelta - 0f) <= float.Epsilon) Compare with 0 { SetPos(m_vServerPos); PlayAction(GetMoveStandAction(false), true, 1, false); bRet = true; } else { SetPos(vPos); } } else // Just move on { // If we have move so far from destination and still don't // receive new 'move' or 'stop move' command, it's better to // stop moving and goto last destination at once if (m_vMoveDir.IsZero()) return false; A3DVECTOR3 vDir = m_vMoveDir; vDir.Normalize(); Quaternion targetRotation = Quaternion.LookRotation(EC_Utility.ToVector3(vDir)); if (Quaternion.Angle(transform.rotation, targetRotation) < 0.5f) transform.rotation = targetRotation; else transform.rotation = Quaternion.Slerp( transform.rotation, targetRotation, rotationSpeed * Time.deltaTime ); vPos = MoveStep(vDir, m_fMoveSpeed, fDeltaTime); SetPos(vPos); float fDist = A3d_Magnitude(m_vServerPos - vCurPos); if (fDist >= MAX_LAGDIST) { SetPos(m_vServerPos); return true; } } return bRet; } public void StopMoveTo(cmd_object_stop_move Cmd) { m_vMoveDir = Cmd.dest - GetPos(); m_bStopMove = true; m_fMoveSpeed = (Cmd.sSpeed) / 256f; m_vStopDir = glb_DecompressDirH(Cmd.dir); SetServerPos(Cmd.dest); float fDist = m_vMoveDir.Normalize(); BrewMonster.BMLogger.Log($"HoangDev : {fDist} : {MAX_LAGDIST} || {m_fMoveSpeed}"); if (fDist >= MAX_LAGDIST || m_fMoveSpeed < 0.01f) { m_bStopMove = false; SetPos(Cmd.dest); PlayAction(GetMoveStandAction(true), true, 1, false); return; } int iMoveMode = Cmd.move_mode; m_cdr.bTraceGround = true; if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_AIR) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_AIR; m_cdr.bTraceGround = false; } else if (((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_WATER) != 0) { m_iMoveEnv = (int)MoveEnvironment.MOVEENV_WATER; m_cdr.bTraceGround = false; } else m_iMoveEnv = (int)MoveEnvironment.MOVEENV_GROUND; switch ((GPMoveMode)iMoveMode & GPMoveMode.GP_MOVE_MASK) { case GPMoveMode.GP_MOVE_WALK: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = false; break; case GPMoveMode.GP_MOVE_RUN: m_iMoveMode = (int)MoveMode.MOVE_MOVE; m_bWalkRun = true; break; case GPMoveMode.GP_MOVE_SLIDE: m_iMoveMode = (int)MoveMode.MOVE_SLIDE; break; case GPMoveMode.GP_MOVE_FALL: m_iMoveMode = (int)MoveMode.MOVE_FREEFALL; m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_FLYFALL: m_cdr.bTraceGround = false; break; case GPMoveMode.GP_MOVE_JUMP: m_iMoveMode = (int)MoveMode.MOVE_JUMP; m_cdr.bTraceGround = false; break; } PlayAction(GetMoveStandAction(true), true, 1, false); } public float GetDistToHost() { return m_fDistToHost; } // Decompress horizontal direction A3DVECTOR3 glb_DecompressDirH(byte byDir) { float fInter = 360.0f / 256.0f; float fRad = Mathf.Deg2Rad * (byDir * fInter); A3DVECTOR3 v; v.x = (float)Math.Cos(fRad); v.z = (float)Math.Sin(fRad); v.y = 0.0f; return v; } public void EnterFightState() { m_FightCnt.Reset(false); } private A3DVECTOR3 MoveStep(A3DVECTOR3 vDir, float fSpeed, float fTime) { A3DVECTOR3 vRealDir = vDir; // OnAirMove only accept positive speed value if (fSpeed < 0.0f) { vRealDir = -vDir; fSpeed = -fSpeed; } m_cdr.vCenter = m_aabbServer.Center; m_cdr.vVelocity = vRealDir * fSpeed; m_cdr.t = fTime; m_cdr.bTestTrnOnly = false; //OtherPlayerMove(m_cdr); A3DVECTOR3 vDelta = m_cdr.t * m_cdr.vVelocity; m_cdr.vCenter += vDelta; m_cdr.vecGroundNormal = g_vAxisY; //if (m_cdr.bTraceGround) // SetGroundNormal(m_cdr.vecGroundNormal); //else // SetGroundNormal(g_vAxisY); return m_cdr.vCenter - g_vAxisY * m_cdr.vExts.y; } void OtherPlayerMove(OtherPlayer_Move_Info OPMoveInfo) { A3DVECTOR3 vDelta = OPMoveInfo.t * OPMoveInfo.vVelocity; OPMoveInfo.vCenter += vDelta; OPMoveInfo.vecGroundNormal = g_vAxisY; A3DVECTOR3 vGroundPos, vNormal; // Now, we directly interpolate the pos, and we don't use bTraceGround //if (OPMoveInfo.bTestTrnOnly) //{ // GetTerrainInfo(OPMoveInfo.vCenter, vGroundPos, vNormal); // vGroundPos.y += OPMoveInfo.vExts.y; // if (OPMoveInfo.vCenter.y < vGroundPos.y + 0.1f) // OPMoveInfo.vecGroundNormal = vNormal; // // verify not below the terrain // if (OPMoveInfo.vCenter.y < vGroundPos.y) // OPMoveInfo.vCenter.y = vGroundPos.y; //} //else //{ // if (VertRayTrace(OPMoveInfo.vCenter, vGroundPos, vNormal, 3.0f)) // { // OPMoveInfo.vecGroundNormal = vNormal; // vGroundPos.y += OPMoveInfo.vExts.y; // // verify not below the ground // if (OPMoveInfo.vCenter.y < vGroundPos.y) // OPMoveInfo.vCenter.y = vGroundPos.y; // } //} } private float A3d_Magnitude(A3DVECTOR3 v) { return Mathf.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z); } // Set server position public void SetServerPos(A3DVECTOR3 vPos) { BrewMonster.BMLogger.Log("SetServerPos "); m_vServerPos = vPos; // If this player is a mule, change it's rider's server pos too. if (m_iBuddyId != 0 && !m_bHangerOn) { var pPlayer = EC_ManMessageMono.Instance.GetECManPlayer.GetElsePlayer(m_iBuddyId); if (pPlayer) pPlayer.SetServerPos(vPos); } } protected override void Update() { base.Update(); MovingTo(Time.deltaTime); if (pHost != null /*&& pHost.IsSkeletonReady()*/) { m_fDistToHost = CalcDist(pHost.GetPos(), true); m_fDistToHostH = CalcDist(pHost.GetPos(), false); } m_pEPWorkMan?.Tick(Time.deltaTime); } public void SetPos(A3DVECTOR3 vPos) { Vector3 vector = new Vector3(); vector.x = vPos.x; vector.y = vPos.y; vector.z = vPos.z; transform.position = vector; m_aabb.Center = vPos + new A3DVECTOR3(0.0f, m_aabb.Extents.y, 0.0f); m_aabb.CompleteMinsMaxs(); m_aabbServer.Center = vPos + new A3DVECTOR3(0.0f, m_aabbServer.Extents.y, 0.0f); m_aabbServer.CompleteMinsMaxs(); } public A3DVECTOR3 GetPos() { A3DVECTOR3 result = new A3DVECTOR3(); result.x = transform.position.x; result.y = transform.position.y; result.z = transform.position.z; return result; } // Get player's real position on server public A3DVECTOR3 GetServerPos() { return m_vServerPos; } public bool ProcessMessage(ECMSG Msg) { switch (Msg.dwMsg) { case long value when value == EC_MsgDef.MSG_PM_PLAYERBASEINFO: OnMsgPlayerBaseInfo(Msg); break; case long value when value == EC_MsgDef.MSG_PM_PLAYERATKRESULT: OnMsgPlayerAtkResult(Msg); break; } return true; } public void HandleRevive(short sReviveType, A3DVECTOR3 pos) { SetServerPos(pos); SetPos(pos); m_pEPWorkMan?.FinishWork(CECEPWork.EP_work_ID.WORK_DEAD); m_pEPWorkMan?.StartNormalWork(new CECEPWorkIdle(m_pEPWorkMan, CECEPWork.Idle_work_type.IDLE_REVIVE, 0, 0)); } async void OnMsgPlayerBaseInfo(ECMSG Msg) { playerbaseinfo_re p = (playerbaseinfo_re)Msg.dwParam1; GRoleBase roleBase = p.Player; // if (p.Roleid != m_PlayerInfo.cid) // { // BMLogger.LogError($"OnMsgPlayerBaseInfo:: Expected {m_PlayerInfo.cid}, but got {p.Roleid}"); // return; // } if (roleBase.name.Length <= 0) return; string strName = Encoding.Unicode.GetString(roleBase.name.ByteArray); SetPlayerBriefInfo(roleBase.cls, roleBase.gender, strName); EventBus.Publish(new NPCINFO(strName, 100,100, p.Roleid)); await SetPlayerModel((byte)roleBase.cls, (byte)roleBase.gender); } void OnMsgPlayerAtkResult(ECMSG Msg) { cmd_object_atk_result pCmd = GPDataTypeHelper.FromBytes((byte[])Msg.dwParam1); //ASSERT(pCmd && pCmd.attacker_id == m_PlayerInfo.cid); // Face to target TurnFaceTo(pCmd.target_id); // TO DO: fix later int attackTime = int.MinValue; PlayAttackEffect(pCmd.target_id, 0, 0, -1, (uint)pCmd.attack_flag, pCmd.speed* 50, ref attackTime); if (!m_pEPWorkMan.FindWork(CECEPWorkMan.Work_type.WT_NORMAL, CECEPWork.EP_work_ID.WORK_HACKOBJECT)){ m_pEPWorkMan.StartNormalWork(new CECEPWorkMelee(m_pEPWorkMan, pCmd.target_id)); } // Enter fight state EnterFightState(); } private async void LoadAppearGfx() { if (!m_pAppearGFX && m_iAppearFlag == (int)PlayerAppearFlag.APPEAR_ENTERWORLD) { CECGFXCaster pGfxCaster = EC_Game.GetGFXCaster(); m_pAppearGFX = await pGfxCaster.LoadGFXEx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_PLAYERAPPEAR)); var pos = GetPos(); m_pAppearGFX.transform.position = new Vector3(pos.x, pos.y, pos.z); m_pAppearGFX.transform.parent = transform; m_pAppearGFX.Play(); } } // Play an attack effect //void PlayAttackEffect(int idTarget, int idSkill, int skillLevel, int nDamage, // uint dwModifier, int nAttackSpeed, int[] piAttackTime/* NULL */, int nSection) //{ // if (!IsAllResReady()) // return; // if (idSkill == 0) // { // int idWeapon = IsShapeChanged() ? 0 : GetWeaponID(); // int nTimeFly = 10; // if (idWeapon != 0) // { // // ¿´¿´ÊDz»ÊÇÔ¶³ÌÎäÆ÷ // DATA_TYPE dt; // WEAPON_ESSENCE pWeapon = (WEAPON_ESSENCE)g_pGame.GetElementDataMan().get_data_ptr(idWeapon, ID_SPACE_ESSENCE, dt); // //ASSERT(dt == DT_WEAPON_ESSENCE); // if (dt == DT_WEAPON_ESSENCE && pWeapon && pWeapon.require_projectile) // { // nTimeFly = 700; // if (m_aEquips[EQUIPIVTR_PROJECTILE]) // idWeapon = m_aEquips[EQUIPIVTR_PROJECTILE]; // set weapon to the projectile // } // } // if (g_pGame.GetGameRun().GetWorld().GetAttacksMan().FindAttackByAttacker(GetPlayerInfo().cid)) // { // // signal early attack event // ClearComActFlagAllRankNodes(true); // } // // melee attack // CECAttackEvent pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddMeleeAttack( // GetPlayerInfo().cid, idTarget, idWeapon, dwModifier, nDamage, nTimeFly); // if (pAttack != null) // { // if (!IsDead() && (dwModifier & CECAttackEvent::MOD_RETORT) == 0 // && (dwModifier & CECAttackEvent::MOD_ATTACK_AURA) == 0 // && PlayAttackAction(nAttackSpeed, piAttackTime, &pAttack.m_bSignaled) // && (dwModifier & CECAttackEvent::MOD_BEAT_BACK) == 0) // { // } // else // { // pAttack.m_bSignaled = true; // } // } // } // else // { // if (skillLevel == 0) // { // if (m_pCurSkill) // skillLevel = m_pCurSkill.GetSkillLevel(); // else // skillLevel = 1; // } // CECAttackEvent pAttack = null; // // first try to find if there is already a skill attack event in attackman // CECAttackerEvents attackerEvents = g_pGame.GetGameRun().GetWorld().GetAttacksMan().FindAttackByAttacker(GetPlayerInfo().cid); // if (attackerEvents) // { // CECAttackEvent pAttack = attackerEvents.Find(idSkill, nSection); // if (() != null) // { // // Ãæ¹¥»÷µÄ·ÇµÚÒ»´ÎÉ˺¦ÏûÏ¢ // pAttack.AddTarget(idTarget, dwModifier, nDamage); // goto EXIT; // } // else // { // attackerEvents.Signal(); // } // } // if (GNET::ElementSkill::IsGoblinSkill(idSkill) && // GNET::ElementSkill::GetType(idSkill) == 2) // { // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( // GetPlayerInfo().cid, GetPlayerInfo().cid, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); // } // else // { // // begin a skill attack // pAttack = g_pGame.GetGameRun().GetWorld().GetAttacksMan().AddSkillAttack( // GetPlayerInfo().cid, m_idCurSkillTarget, idTarget, GetWeaponID(), idSkill, skillLevel, dwModifier, nDamage); // } // if (pAttack) // { // pAttack.SetSkillSection(nSection); // if (!IsDead() && (dwModifier & CECAttackEvent::MOD_RETORT) == 0 // && (dwModifier & CECAttackEvent::MOD_ATTACK_AURA) == 0 // && PlaySkillAttackAction(idSkill, nAttackSpeed, NULL, nSection, &pAttack.m_bSignaled) // && (dwModifier & CECAttackEvent::MOD_BEAT_BACK) == 0) // { // } // else // { // pAttack.m_bSignaled = true; // } // } // EXIT: // // For skill attacking, time is always set to 0 // if (piAttackTime) // *piAttackTime = 0; // } //} //public A3DVECTOR3 GetPos() //{ // return new A3DVECTOR3(transform.position.x, transform.position.y, transform.position.z); //} private void SetPlayerBriefInfo(int iProf, int iGender, string szName) { m_iProfession = iProf; m_iGender = iGender; m_bBaseInfoReady = true; } // Level up public void LevelUp() { // if (m_pLevelUpGFX) // m_pLevelUpGFX->Start(true); // PlayGfx(EC_Resource.res_GFXFile((int)GfxResourceType.RES_GFX_LEVELUP), null, 1f,1);//PLAYERMODEL_TYPEALL } } // Player appear flag public enum PlayerAppearFlag { APPEAR_DISAPPEAR = -1, // Player disappear APPEAR_ENTERWORLD = 0, // Player join world APPEAR_RUNINTOVIEW, // Player run into view APPEAR_GHOST, // Player is in ghost state, in player list but not active }; public struct OtherPlayer_Move_Info { // Bounding sphere of avator public A3DVECTOR3 vCenter; public A3DVECTOR3 vExts; public float fStepHeight; // Velocity public A3DVECTOR3 vVelocity; // time span ( sec ) public float t; public bool bTraceGround; // Whether trace the ground public bool bTestTrnOnly; // Trace terrain only public A3DVECTOR3 vecGroundNormal; // if bTraceGround is true, this will contain the ground normal when returned } public class A3DAABB { public A3DVECTOR3 Center; public A3DVECTOR3 Extents; public A3DVECTOR3 Mins; public A3DVECTOR3 Maxs; public A3DAABB() { } public A3DAABB(A3DAABB aabb) { Center = aabb.Center; Extents = aabb.Extents; Mins = aabb.Mins; Maxs = aabb.Maxs; } public A3DAABB(A3DVECTOR3 mins, A3DVECTOR3 maxs) { Mins = mins; Maxs = maxs; Center = (mins + maxs) * 0.5f; Extents = maxs - Center; } // Reset AABB về trạng thái rỗng public void Clear() { Mins = new A3DVECTOR3(999999f, 999999f, 999999f); Maxs = new A3DVECTOR3(-999999f, -999999f, -999999f); Center = new A3DVECTOR3(0f, 0f, 0f); Extents = new A3DVECTOR3(0f, 0f, 0f); } // Thêm 1 điểm vào AABB public void AddVertex(A3DVECTOR3 v) { if (v.x < Mins.x) Mins.x = v.x; if (v.y < Mins.y) Mins.y = v.y; if (v.z < Mins.z) Mins.z = v.z; if (v.x > Maxs.x) Maxs.x = v.x; if (v.y > Maxs.y) Maxs.y = v.y; if (v.z > Maxs.z) Maxs.z = v.z; CompleteCenterExts(); } // Hợp nhất 2 AABB public void Merge(A3DAABB subAABB) { if (subAABB.Mins.x < Mins.x) Mins.x = subAABB.Mins.x; if (subAABB.Mins.y < Mins.y) Mins.y = subAABB.Mins.y; if (subAABB.Mins.z < Mins.z) Mins.z = subAABB.Mins.z; if (subAABB.Maxs.x > Maxs.x) Maxs.x = subAABB.Maxs.x; if (subAABB.Maxs.y > Maxs.y) Maxs.y = subAABB.Maxs.y; if (subAABB.Maxs.z > Maxs.z) Maxs.z = subAABB.Maxs.z; CompleteCenterExts(); } // Cập nhật Mins, Maxs từ Center + Extents public void CompleteMinsMaxs() { Mins = Center - Extents; Maxs = Center + Extents; } // Cập nhật Center + Extents từ Mins, Maxs public void CompleteCenterExts() { Center = (Mins + Maxs) * 0.5f; Extents = Maxs - Center; } // Kiểm tra điểm có nằm trong AABB không public bool IsPointIn(A3DVECTOR3 v) { return !(v.x > Maxs.x || v.x < Mins.x || v.y > Maxs.y || v.y < Mins.y || v.z > Maxs.z || v.z < Mins.z); } // Kiểm tra 1 AABB khác có nằm trong AABB này không public bool IsAABBIn(A3DAABB aabb) { return (aabb.Mins.x >= Mins.x && aabb.Maxs.x <= Maxs.x && aabb.Mins.y >= Mins.y && aabb.Maxs.y <= Maxs.y && aabb.Mins.z >= Mins.z && aabb.Maxs.z <= Maxs.z); } // Xây AABB từ một tập vertices public void Build(A3DVECTOR3[] vertices) { Clear(); foreach (var v in vertices) AddVertex(v); } // Lấy các vertices (8 điểm) của AABB public A3DVECTOR3[] GetVertices() { A3DVECTOR3[] verts = new A3DVECTOR3[8]; verts[0] = new A3DVECTOR3(Mins.x, Mins.y, Mins.z); verts[1] = new A3DVECTOR3(Maxs.x, Mins.y, Mins.z); verts[2] = new A3DVECTOR3(Maxs.x, Maxs.y, Mins.z); verts[3] = new A3DVECTOR3(Mins.x, Maxs.y, Mins.z); verts[4] = new A3DVECTOR3(Mins.x, Mins.y, Maxs.z); verts[5] = new A3DVECTOR3(Maxs.x, Mins.y, Maxs.z); verts[6] = new A3DVECTOR3(Maxs.x, Maxs.y, Maxs.z); verts[7] = new A3DVECTOR3(Mins.x, Maxs.y, Maxs.z); return verts; } } }