/// <summary> /// Returns closest hit from line start position. /// </summary> public bool GetIntersectionWithLine(ref LineD line, ref MyCharacterHitInfo info, IntersectionFlags flags = IntersectionFlags.ALL_TRIANGLES) { // TODO: This now uses caspule of physics rigid body on the character, it needs to be changed to ragdoll // Currently this approach will be used to support Characters with different skeleton than humanoid if (info == null) info = new MyCharacterHitInfo(); info.Reset(); bool capsulesReady = UpdateCapsuleBones(); if (!capsulesReady) return false; double closestDistanceToHit = double.MaxValue; Vector3D hitPosition = Vector3D.Zero; Vector3D hitPosition2 = Vector3D.Zero; Vector3 hitNormal = Vector3.Zero; Vector3 hitNormal2 = Vector3.Zero; int capsuleIndex = -1; for (int i = 0; i < m_bodyCapsules.Length; i++) { CapsuleD capsule = m_bodyCapsules[i]; if (capsule.Intersect(line, ref hitPosition, ref hitPosition2, ref hitNormal, ref hitNormal2)) { double distanceToHit = Vector3.Distance(hitPosition, line.From); if (distanceToHit >= closestDistanceToHit) continue; closestDistanceToHit = distanceToHit; capsuleIndex = i; } } if (capsuleIndex != -1) { Matrix worldMatrix = PositionComp.WorldMatrix; int boneIndex = FindBestBone(capsuleIndex, ref hitPosition, ref worldMatrix); // Transform line to model static position and compute accurate collision there // 1. Transform line in local coordinates (used later) Matrix worldMatrixInv = PositionComp.WorldMatrixNormalizedInv; Vector3 fromTrans = Vector3.Transform(line.From, ref worldMatrixInv); Vector3 toTrans = Vector3.Transform(line.To, ref worldMatrixInv); LineD lineLocal = new LineD(fromTrans, toTrans); // 2. Transform line to to bone pose in binding position var bone = AnimationController.CharacterBones[boneIndex]; bone.ComputeAbsoluteTransform(); Matrix boneAbsTrans = bone.AbsoluteTransform; Matrix skinTransform = bone.SkinTransform; Matrix boneTrans = skinTransform * boneAbsTrans; Matrix invBoneTrans = Matrix.Invert(boneTrans); fromTrans = Vector3.Transform(fromTrans, ref invBoneTrans); toTrans = Vector3.Transform(toTrans, ref invBoneTrans); // 3. Move back line to world coordinates LineD lineTransWorld = new LineD(Vector3.Transform(fromTrans, ref worldMatrix), Vector3.Transform(toTrans, ref worldMatrix)); MyIntersectionResultLineTriangleEx? triangle_; bool success = base.GetIntersectionWithLine(ref lineTransWorld, out triangle_, flags); if (success) { MyIntersectionResultLineTriangleEx triangle = triangle_.Value; info.CapsuleIndex = capsuleIndex; info.BoneIndex = boneIndex; info.Capsule = m_bodyCapsules[info.CapsuleIndex]; info.HitHead = info.CapsuleIndex == 0 && m_bodyCapsules.Length > 1; info.HitPositionBindingPose = triangle.IntersectionPointInObjectSpace; info.HitNormalBindingPose = triangle.NormalInObjectSpace; // 4. Move intersection from binding to dynamic pose MyTriangle_Vertexes vertices = new MyTriangle_Vertexes(); vertices.Vertex0 = Vector3.Transform(triangle.Triangle.InputTriangle.Vertex0, ref boneTrans); vertices.Vertex1 = Vector3.Transform(triangle.Triangle.InputTriangle.Vertex1, ref boneTrans); vertices.Vertex2 = Vector3.Transform(triangle.Triangle.InputTriangle.Vertex2, ref boneTrans); Vector3 triangleNormal = Vector3.TransformNormal(triangle.Triangle.InputTriangleNormal, boneTrans); MyIntersectionResultLineTriangle triraw = new MyIntersectionResultLineTriangle(ref vertices, ref triangleNormal, triangle.Triangle.Distance); Vector3 intersectionLocal = Vector3.Transform(triangle.IntersectionPointInObjectSpace, ref boneTrans); Vector3 normalLocal = Vector3.TransformNormal(triangle.NormalInObjectSpace, boneTrans); // 5. Store results triangle = new MyIntersectionResultLineTriangleEx(); triangle.Triangle = triraw; triangle.IntersectionPointInObjectSpace = intersectionLocal; triangle.NormalInObjectSpace = normalLocal; triangle.IntersectionPointInWorldSpace = Vector3.Transform(intersectionLocal, ref worldMatrix); triangle.NormalInWorldSpace = Vector3.TransformNormal(normalLocal, worldMatrix); triangle.InputLineInObjectSpace = lineLocal; info.Triangle = triangle; if (MyDebugDrawSettings.ENABLE_DEBUG_DRAW) { MyRenderProxy.DebugClearPersistentMessages(); MyRenderProxy.DebugDrawCapsule(info.Capsule.P0, info.Capsule.P1, info.Capsule.Radius, Color.Aqua, false, persistent: true); Vector3 p0Local = Vector3.Transform(info.Capsule.P0, ref worldMatrixInv); Vector3 p1Local = Vector3.Transform(info.Capsule.P1, ref worldMatrixInv); Vector3 p0LocalTrans = Vector3.Transform(p0Local, ref invBoneTrans); Vector3 p1LocalTrans = Vector3.Transform(p1Local, ref invBoneTrans); MyRenderProxy.DebugDrawCapsule(Vector3.Transform(p0LocalTrans, ref worldMatrix), Vector3.Transform(p1LocalTrans, ref worldMatrix), info.Capsule.Radius, Color.Brown, false, persistent: true); MyRenderProxy.DebugDrawLine3D(line.From, line.To, Color.Blue, Color.Red, false, true); MyRenderProxy.DebugDrawLine3D(lineTransWorld.From, lineTransWorld.To, Color.Green, Color.Yellow, false, true); MyRenderProxy.DebugDrawSphere(triangle.IntersectionPointInWorldSpace, 0.02f, Color.Red, 1, false, persistent: true); MyRenderProxy.DebugDrawAxis((MatrixD)boneTrans * WorldMatrix, 0.1f, false, true, true); } return true; } } return false; }
private void GetHitEntityAndPosition(LineD line, out IMyEntity entity, out Vector3D hitPosition, out Vector3 hitNormal, out MyCharacterHitInfo charHitInfo) { entity = null; hitPosition = hitNormal = Vector3.Zero; charHitInfo = null; // 1. rough raycast int raycastListIndex = 0; do { if (entity == null) { if (raycastListIndex == 0) // cast only the first iteration { ProfilerShort.Begin("MyGamePruningStructure::CastProjectileRay"); MyPhysics.CastRay(line.From, line.To, m_raycastResult, MyPhysics.CollisionLayers.DefaultCollisionLayer); ProfilerShort.End(); } if (raycastListIndex < m_raycastResult.Count) { MyPhysics.HitInfo hitInfo = m_raycastResult[raycastListIndex]; entity = hitInfo.HkHitInfo.GetHitEntity() as MyEntity; hitPosition = hitInfo.Position; hitNormal = hitInfo.HkHitInfo.Normal; } } // 2. prevent shooting through characters, retest trajectory between entity and player if (!(entity is MyCharacter) || entity == null) { // first: raycast, get all entities in line, limit distance if possible LineD lineLimited = new LineD(line.From, entity == null ? line.To : hitPosition); if (m_entityRaycastResult == null) { m_entityRaycastResult = new List<MyLineSegmentOverlapResult<MyEntity>>(16); } else { m_entityRaycastResult.Clear(); } MyGamePruningStructure.GetAllEntitiesInRay(ref lineLimited, m_entityRaycastResult); // second: precise tests, find best result double bestDistanceSq = double.MaxValue; IMyEntity entityBest = null; for (int i = 0; i < m_entityRaycastResult.Count; i++) { if (m_entityRaycastResult[i].Element is MyCharacter) { MyCharacter hitCharacter = m_entityRaycastResult[i].Element as MyCharacter; bool intersection = hitCharacter.GetIntersectionWithLine(ref line, ref m_charHitInfo); if (intersection) { double distanceSq = Vector3D.DistanceSquared(m_charHitInfo.Triangle.IntersectionPointInWorldSpace, line.From); if (distanceSq < bestDistanceSq && !IsIgnoredEntity(hitCharacter)) { bestDistanceSq = distanceSq; entityBest = hitCharacter; hitPosition = m_charHitInfo.Triangle.IntersectionPointInWorldSpace; hitNormal = m_charHitInfo.Triangle.NormalInWorldSpace; charHitInfo = m_charHitInfo; } } } } // finally: do we have best result? then return it if (entityBest != null) { entity = entityBest; return; // this was precise result, so return } } // 3. nothing found in the precise test? then fallback to already found results if (entity == null) return; // no fallback results if (entity is MyCharacter) // retest character found in fallback { MyCharacter hitCharacter = entity as MyCharacter; bool intersection = hitCharacter.GetIntersectionWithLine(ref line, ref m_charHitInfo); if (intersection) { hitPosition = m_charHitInfo.Triangle.IntersectionPointInWorldSpace; hitNormal = m_charHitInfo.Triangle.NormalInWorldSpace; charHitInfo = m_charHitInfo; } else { entity = null; // no hit. } } else if (entity is MyGhostCharacter) { MyHitInfo info = new MyHitInfo(); info.Position = hitPosition; info.Normal = hitNormal; } else { MyCubeGrid grid = entity as MyCubeGrid; if (grid != null) { MyIntersectionResultLineTriangleEx? result; bool success = grid.GetIntersectionWithLine(ref line, out result); if (success && result.HasValue) { hitPosition = result.Value.IntersectionPointInWorldSpace; hitNormal = result.Value.NormalInWorldSpace; if (Vector3.Dot(hitNormal, line.Direction) > 0) hitNormal = -hitNormal; } MyHitInfo info = new MyHitInfo(); info.Position = hitPosition; info.Normal = hitNormal; } } } while (entity == null && ++raycastListIndex < m_entityRaycastResult.Count); }