using BrewMonster.Scripts.Ornament; using BrewMonster.Scripts.Player; using BrewMonster.Scripts.World; using CSNetwork.GPDataType; using System; using UnityEngine; using WORD = System.UInt16; namespace BrewMonster.Scripts { public static class EC_CDR { // Cho phép CECHostMove gán mask theo scene (giữ linh hoạt nhưng không phá cấu trúc) public static LayerMask BrushMask { get; set; } = 1<<7; public static LayerMask TerrainMask { get; set; } = 1<<6; const float LOCAL_EPSILON = 1e-5f; //[Flags] public class CDR_EVN { public const int CDR_BRUSH = 0x1, CDR_TERRAIN = 0x2, CDR_WATER = 0x4; } static LayerMask UsedMask_Ground() => BrushMask | TerrainMask; static bool CollideWithEnv(env_trace_t pEnvTrc) { pEnvTrc.fFraction = 100.0f; pEnvTrc.bStartSolid = false; pEnvTrc.dwClsFlag = 0; if ((pEnvTrc.dwCheckFlag & CDR_EVN.CDR_BRUSH) == CDR_EVN.CDR_BRUSH) { BrushTraceInfo bruInfo = new BrushTraceInfo(); bruInfo.Init(pEnvTrc.vStart, pEnvTrc.vDelta, pEnvTrc.vExt); if (AABBCollideWithBrush(ref bruInfo)) { pEnvTrc.fFraction = bruInfo.fFraction; pEnvTrc.vHitNormal = bruInfo.ClipPlane.GetNormal(); pEnvTrc.bStartSolid = bruInfo.bStartSolid; pEnvTrc.dwClsFlag = CDR_EVN.CDR_BRUSH; } } if ((pEnvTrc.dwCheckFlag & CDR_EVN.CDR_TERRAIN) == CDR_EVN.CDR_TERRAIN) { float fFraction = 0f; A3DVECTOR3 vTerNormal = new A3DVECTOR3(); bool bStart = false; if (CollideWithTerrain(pEnvTrc.vTerStart, pEnvTrc.vDelta, ref fFraction, ref vTerNormal, ref bStart) && (fFraction < pEnvTrc.fFraction)) { //assert(fFraction >= 0.0f); //pEnvTrc.fFraction = a_Max(0.0f, fFraction - 1E-4f); pEnvTrc.fFraction = fFraction; pEnvTrc.vHitNormal = vTerNormal; pEnvTrc.bStartSolid = bStart; pEnvTrc.dwClsFlag = CDR_EVN.CDR_TERRAIN; } } if ((pEnvTrc.dwCheckFlag & CDR_EVN.CDR_WATER) == CDR_EVN.CDR_WATER) { float fFraction = 0f; A3DVECTOR3 vWatNormal = new A3DVECTOR3(); bool bStart = false; //@todo : TBD: use center or foot? By Kuiwu[10/10/2005] //if (CollideWithWater(pEnvTrc.vWatStart, pEnvTrc.vDelta, pEnvTrc.bWaterSolid, ref fFraction, ref vWatNormal, ref bStart) // && fFraction < pEnvTrc.fFraction) //{ // pEnvTrc.fFraction = fFraction; // pEnvTrc.vHitNormal = vWatNormal; // pEnvTrc.bStartSolid = bStart; // pEnvTrc.dwClsFlag = CDR_EVN.CDR_WATER; // //# ifdef CDR_DEBUG // // A3DVECTOR3 vHitPos(pEnvTrc.vWatStart +pEnvTrc.vDelta * fFraction); // // sprintf(msg, "collide water, fraction %f pos %f %f %f \n", fFraction, vHitPos.x, vHitPos.y, vHitPos.z); // // OUTPUT_DEBUG_INFO(msg); // //#endif //} } return (pEnvTrc.fFraction < 1.0f + 1E-4f); } // == Thay CollideWithEnv (C++) bằng BoxCast == //static bool CollideWithEnv_BoxCast(Vector3 vStart, Vector3 vDelta, Vector3 vExt, // LayerMask mask, // out RaycastHit hit, out float fFraction, out Vector3 vHitNormal, out bool bStartSolid, // float skin = 0.01f) //{ // hit = default; // vHitNormal = Vector3.up; // bStartSolid = false; // fFraction = 0.0f; // float dist = vDelta.magnitude; // if (dist <= 1e-6f) return false; // var hasHit = Physics.Raycast(vStart, vDelta,out hit, dist, mask); // if (hasHit) // { // bStartSolid = true; // fFraction = (t - 5E-4f) / vDelta.Normalize(); // return true; // } // // start-in-solid // var overlapped = Physics.OverlapBox(vStart, vExt - Vector3.one * skin, Quaternion.identity, mask, QueryTriggerInteraction.Ignore); // if (overlapped != null && overlapped.Length > 0) // { // bStartSolid = true; // return true; // } // // sweep AABB // Vector3 dir = vDelta / Mathf.Max(dist, 1e-6f); // if (Physics.BoxCast(vStart, vExt - Vector3.one * skin, dir, out hit, Quaternion.identity, dist, mask, QueryTriggerInteraction.Ignore)) // { // fFraction = Mathf.Clamp01(hit.distance / Mathf.Max(dist, 1e-6f)); // vHitNormal = hit.normal; // return true; // } // return false; //} // == Thay RetrieveSupportPlane (C++) bằng Raycast xuống == static bool DoGroundProbe(Vector3 vStart, Vector3 vExt, float fDeltaY, LayerMask mask, out Vector3 vEnd, out Vector3 vHitNormal, out bool bSupport, float skin = 0.01f) { vEnd = vStart; vHitNormal = Vector3.up; bSupport = false; float dist = Mathf.Max(fDeltaY, 0f) + vExt.y + skin + 0.05f; Vector3 origin = vStart + Vector3.up * 0.05f; if (Physics.Raycast(origin, Vector3.down, out RaycastHit hit, dist, mask, QueryTriggerInteraction.Ignore)) { vHitNormal = hit.normal; vEnd = new Vector3(vStart.x, hit.point.y + vExt.y + skin, vStart.z); bSupport = (vHitNormal.y >= 0f); return true; } return true; // không thấy ground → bSupport=false } // ======= STATIC OnGroundMove – GIỮ NGUYÊN VAI TRÒ TOÀN CỤC (C API) ======= public static void OnGroundMove(ref CDR_INFO CDRInfo) { const float VEL_EPSILON = 1e-4f; const float DIST_EPSILON = 1e-4f; const float NORMAL_EPSILON = 1e-2f; const float VEL_MAX_SPEED = 200f; const float VEL_REFLECT = 0.3f; CDRInfo.fMoveDist = 0.0f; bool bFreeFall = (CDRInfo.vTPNormal.y < CDRInfo.fSlopeThresh); if (Mathf.Abs(CDRInfo.fYVel) < VEL_EPSILON && Mathf.Abs(CDRInfo.fSpeed) < VEL_EPSILON && !bFreeFall) return; float fYVel = CDRInfo.fYVel; bool bJump = (fYVel > 0.5f); Vector3 vVelocity = CDRInfo.fSpeed * new Vector3(CDRInfo.vXOZVelDir.x, 0f, CDRInfo.vXOZVelDir.z) + fYVel * Vector3.up; if (bFreeFall) { vVelocity += -CDRInfo.fGravityAccel * CDRInfo.t * Vector3.up; fYVel += -CDRInfo.fGravityAccel * CDRInfo.t; } Vector3 vVelDir = vVelocity; float fVelSpeed = vVelDir.magnitude; //vVelDir = vVelDir.normalized; if (fVelSpeed > 1e-6f) { vVelDir /= fVelSpeed; } else { vVelDir = Vector3.zero; } if (!bFreeFall) { fVelSpeed = Mathf.Min(fVelSpeed, VEL_MAX_SPEED); } vVelocity = vVelDir * fVelSpeed; float dtp = Vector3.Dot(vVelDir, CDRInfo.vTPNormal); if (dtp < 0f || !bJump) { vVelocity = (vVelDir - CDRInfo.vTPNormal * dtp - CDRInfo.vTPNormal * dtp * 0.01f) * fVelSpeed; } CDRInfo.vAbsVelocity = vVelocity; Vector3 vStart = CDRInfo.vCenter; Vector3 vExt = CDRInfo.vExtent; float fTime = CDRInfo.t; Vector3 vDelta, vNormal = Vector3.up, vFinalPos = vStart; bool bPull = false; bool bTryPull = false; int nTry = 0; LayerMask mask = UsedMask_Ground(); while (nTry < 1) { vDelta = vVelocity * fTime; float fDeltaDist = vDelta.magnitude; //if (fDeltaDist < DIST_EPSILON) break; // TO DO: fix later beacuse logic in CollideWithEnv_BoxCast is wrong //bool hasHit = CollideWithEnv_BoxCast(vStart, vDelta, vExt, mask, // out RaycastHit hit, out float fFraction, out Vector3 hitNormal, out bool bStartSolid); Vector3 posFoot = vStart - Vector3.up * vExt.y; Debug.DrawLine(vStart, posFoot + vVelocity, Color.yellow, 10f); bool bClear = !Physics.Raycast(posFoot, (posFoot + vVelocity).normalized, out RaycastHit hit, fDeltaDist, mask); nTry++; if (bClear) { vFinalPos = vStart + vDelta; Debug.DrawLine(vStart, vFinalPos, Color.red, 10f); CDRInfo.fMoveDist += fDeltaDist; break; } Debug.LogError("Hit"); //if (bStartSolid) //{ // CDRInfo.fMoveDist = 0f; // if (CDRInfo.vTPNormal.y < CDRInfo.fSlopeThresh) CDRInfo.vTPNormal = Vector3.up; // return; //} vStart = hit.point + Vector3.up * vExt.y; vFinalPos = vStart; //CDRInfo.fMoveDist += (fDeltaDist * fFraction); //fTime -= fTime * fFraction; //vNormal = hitNormal; // Step-up (giữ tinh thần bản gốc) if (!bFreeFall && !bTryPull && !bJump) { //float skin = 0.01f; posFoot = vStart - Vector3.up * vExt.y; Vector3 vStartUp = new Vector3(0f, CDRInfo.fStepHeight, 0f); bPull = !Physics.Raycast(posFoot, (posFoot + vStartUp).normalized, out hit, mask); if (bPull) { vStart += Vector3.up * CDRInfo.fStepHeight; posFoot = vStart - Vector3.up * vExt.y; Vector3 vDelta2 = vVelocity; bool bMove = !Physics.Raycast(posFoot, (posFoot + vVelocity).normalized, out hit, mask); if (!bMove) { //vDelta2 *= frac2; vFinalPos = hit.point + Vector3.up * vExt.y; } //if (vDelta2.sqrMagnitude < (vExt.x * vExt.x * 4f)) //{ // vStart -= Vector3.up * CDRInfo.fStepHeight; // bPull = false; //} } bTryPull = true; } //if (!bPull) //{ // if (vVelocity.sqrMagnitude > 1e-12f) // { // vVelDir = vVelocity.normalized; // fVelSpeed = vVelocity.magnitude * (1f - nTry * 0.1f); // dtp = Vector3.Dot(vNormal, vVelDir); // float fRelSpeed = Mathf.Min(fVelSpeed, 5.0f); // if (dtp >= 0f && dtp < 1e-4f) // { // vVelocity += vNormal * VEL_REFLECT * fRelSpeed; // } // else // { // vVelocity = (vVelDir - vNormal * dtp) * fVelSpeed - vNormal * dtp * VEL_REFLECT * fRelSpeed; // } // } // if (fYVel > VEL_EPSILON) // { // if (vNormal.y >= CDRInfo.fSlopeThresh || vNormal.y < -NORMAL_EPSILON) fYVel = 0f; // } // else if (fYVel < -VEL_EPSILON) // { // if (vNormal.y >= CDRInfo.fSlopeThresh) fYVel = 0f; // } //} } // “vertical ground trace” – thay RetrieveSupportPlane Vector3 vTPNormal = Vector3.zero; Vector3 vFinal = vFinalPos; float downDist = 0.3f; if (bPull) downDist = CDRInfo.fStepHeight + 0.1f; if (bJump) downDist = 0.0f; if (downDist > 0f) { if (!DoGroundProbe(vFinalPos, vExt, downDist, mask, out Vector3 vEnd, out Vector3 groundNormal, out bool bSupport)) { CDRInfo.fMoveDist = 0f; CDRInfo.vTPNormal = Vector3.up; return; } if (bSupport) { vFinal = vEnd; if (!bJump) vTPNormal = groundNormal; } } if ((vTPNormal.y >= CDRInfo.fSlopeThresh && fYVel < 0.0f) || (!bJump && fYVel > 0.0f)) fYVel = 0.0f; CDRInfo.vCenter = vFinal; CDRInfo.fYVel = fYVel; if (vTPNormal != Vector3.zero) CDRInfo.vTPNormal = vTPNormal; } static bool SegmentTriangleIntersect(A3DVECTOR3 vStart, A3DVECTOR3 vDelta, A3DVECTOR3[] vert, ref float fFraction, bool bCull) { float dist = 0f; A3DVECTOR3 vDir = new A3DVECTOR3(vDelta); dist = vDir.Normalize(); if (dist < 1E-5f) { //assert(0 && "too small dist!"); fFraction = 0.0f; return true; } float t = 0f, u = 0f, v = 0f; if (RayTriangleIntersect(vStart, vDir, vert, ref t, ref u, ref v, bCull) && (t >= 0.0f) && (t <= dist)) { //fFraction = t/dist; //fFraction = a_Max( 0.0f, fFraction -1E-4f); //put back fFraction = (t - 5E-4f) / dist; AAssist.a_ClampFloor(ref fFraction, 0.0f); return true; } return false; } /* * ray: origin + t* dir, triangle: p(u,v) = (1-u -v)vert[0] + u*vert[1] + v*vert[2] * @desc : * @param vDir: normalized direction * @param bCull: cull back face if true * @return : * @note: * @todo: * @author: kuiwu [8/10/2005] * @ref: Tomas Moller's JGT code */ static bool RayTriangleIntersect(A3DVECTOR3 vOrigin, A3DVECTOR3 vDir, A3DVECTOR3[] vert, ref float t, ref float u, ref float v, bool bCull) { // find vectors for two edges sharing vert0 A3DVECTOR3 edge1 = vert[1] - vert[0]; A3DVECTOR3 edge2 = vert[2] - vert[0]; // begin calculating determinant - also used to calculate U parameter A3DVECTOR3 pvec = A3DVECTOR3.CrossProduct(vDir, edge2); // if determinant is near zero, ray lies in plane of triangle float det = A3DVECTOR3.DotProduct(edge1, pvec); if (bCull) { if (det < LOCAL_EPSILON) return false; // From here, det is > 0. // Calculate distance from vert0 to ray origin A3DVECTOR3 tvec = vOrigin - vert[0]; // Calculate U parameter and test bounds u = A3DVECTOR3.DotProduct(tvec, pvec); if ((u < 0.0f) || (u > det)) return false; // prepare to test V parameter A3DVECTOR3 qvec = A3DVECTOR3.CrossProduct(tvec, edge1); // calculate V parameter and test bounds v = A3DVECTOR3.DotProduct(vDir, qvec); if ((v < 0.0f) || (u + v > det)) return false; // calculate t, ray intersects triangle t = A3DVECTOR3.DotProduct(edge2, qvec); // Det > 0 so we can early exit here // Intersection point is valid if distance is positive // (else it can just be a face behind the orig point) if (t < 0.0f) { return false; } float OneOverDet = 1.0f / det; t *= OneOverDet; u *= OneOverDet; v *= OneOverDet; } else { if (det > -LOCAL_EPSILON && det < LOCAL_EPSILON) return false; float OneOverDet = 1.0f / det; // Calculate distance from vert0 to ray origin A3DVECTOR3 tvec = vOrigin - vert[0]; // calculate U parameter and test bounds u = A3DVECTOR3.DotProduct(tvec, pvec) * OneOverDet; if ((u < 0.0f) || (u > 1.0f)) return false; // prepare to test V parameter A3DVECTOR3 qvec = A3DVECTOR3.CrossProduct(tvec, edge1); // calculate V parameter and test bounds v = A3DVECTOR3.DotProduct(vDir, qvec) * OneOverDet; if ((v < 0.0f) || (u + v > 1.0f)) return false; // calculate t, ray intersects triangle t = A3DVECTOR3.DotProduct(edge2, qvec) * OneOverDet; } return true; } public static bool CollideWithTerrain(A3DVECTOR3 vStart, A3DVECTOR3 vDelta, ref float fFraction, ref A3DVECTOR3 vHitNormal, ref bool bStart) { CECWorld pWorld = CECWorld.Instance; //g_pGame.GetGameRun().GetWorld(); A3DTerrain2 pTerrain = pWorld.GetTerrain(); bStart = false; float h1 = pTerrain.GetPosHeight(vStart, ref vHitNormal); if (h1 > vStart.y + 1E-4f) {//start under terrain bStart = true; fFraction = 0.0f; return true; } int nWid, nHei; // in grid, 2 meters float fMag = vDelta.Magnitude(); nWid = (int)Math.Ceiling(fMag / 2.0f); nWid = Math.Max(3, nWid); nHei = nWid; int nTriangles = nWid * nHei * 2; A3DVECTOR3[] pVerts = new A3DVECTOR3[(nWid + 1) * (nHei + 1)]; //assert(pVerts != NULL); //memset(pVerts, 0, sizeof(A3DVECTOR3)* (nWid + 1) * (nHei + 1)); WORD[] pIndices = new WORD[nTriangles * 3]; //assert(pIndices != NULL); //memset(pIndices, 0, sizeof(WORD)* nTriangles * 3); if (!pTerrain.GetFacesOfArea(vStart, nWid, nHei, ref pVerts, ref pIndices)) { //a_freetemp(pVerts); // a_freetemp(pIndices); return false; } int i; A3DVECTOR3[] vert = new A3DVECTOR3[3]; //@note : Here init the fraction. By Kuiwu[9/10/2005] fFraction = 100.0f; float tmpFraction = fFraction; for (i = 0; i < nTriangles; i++) { vert[0] = pVerts[pIndices[i * 3]]; vert[1] = pVerts[pIndices[i * 3 + 1]]; vert[2] = pVerts[pIndices[i * 3 + 2]]; //A3DVECTOR3 vPt; //@note: Tomas Moller's JGT code : By Kuiwu[9/10/2005] //@note: discard the engine version because it put back the hit point too much. By Kuiwu[13/10/2005] // if(CLS_RayToTriangle(vStart, vDelta, *vert[0], *vert[1], *vert[2], vPt, true, &tmpFraction) // && (tmpFraction <= 1.0f) && (tmpFraction < fFraction)) if (SegmentTriangleIntersect(vStart, vDelta, vert, ref tmpFraction, true) && (tmpFraction < fFraction)) { //get the triangle normal A3DVECTOR3 vEdge1 = vert[1] - vert[0]; A3DVECTOR3 vEdge2 = vert[2] - vert[0]; vHitNormal = A3DVECTOR3.CrossProduct(vEdge1, vEdge2); vHitNormal.Normalize(); //@note : may be redundant, but to assure. By Kuiwu[17/10/2005] A3DVECTOR3 vDir = new A3DVECTOR3(); A3DVECTOR3.Normalize(vDelta, out vDir); if (A3DVECTOR3.DotProduct(vHitNormal, vDir) > 0.01f) {//leave the hit plane //assert(0 && "hit a plane with same direction!"); continue; } fFraction = Math.Max(0.0f, tmpFraction); } } //a_freetemp(pVerts); // a_freetemp(pIndices); return (fFraction <= 1.0f); } public static bool AABBCollideWithBrush(ref BrushTraceInfo pInfo) { //TO DO: fix later return false; //CECWorld pWorld = CECWorld.Instance; //g_pGame.GetGameRun().GetWorld(); //CECOrnamentMan pOrnMan = pWorld.GetOrnamentMan(); //bool bBrush = pOrnMan.TraceWithBrush(ref pInfo); //BrushTraceInfo info = pInfo; //CECMatterMan pMatterMan = pWorld.GetMatterMan(); //if (pMatterMan.TraceWithBrush(ref info) && info.fFraction < pInfo.fFraction) //{ // pInfo = info; // bBrush = true; //} //info = pInfo; //CECNPCMan pNPCMan = pWorld.GetNPCMan(); //if (pNPCMan.TraceWithBrush(ref info) && info.fFraction < pInfo.fFraction) //{ // pInfo = info; // bBrush = true; //} //return bBrush; } } public struct OtherPlayer_Move_Info { // Bounding sphere of avator public A3DVECTOR3 vCenter; public A3DVECTOR3 vExts; public float fStepHeight; public A3DVECTOR3 vVelocity; 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 }; //@desc :used to trace the environment, brush&terrain&water By Kuiwu[8/10/2005] public struct env_trace_t { public A3DVECTOR3 vStart; // brush start public A3DVECTOR3 vExt; public A3DVECTOR3 vDelta; public A3DVECTOR3 vTerStart; public A3DVECTOR3 vWatStart; public uint dwCheckFlag; public bool bWaterSolid; public float fFraction; public A3DVECTOR3 vHitNormal; public bool bStartSolid; //start in solid public uint dwClsFlag; //collision flag }; }