< Summary

Class:ECSSystems.ECSRaycastSystem.ECSRaycastSystem
Assembly:ECS7Plugin.Systems.Raycast
File(s):/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/Systems/RaycastSystem/ECSRaycastSystem.cs
Covered lines:124
Uncovered lines:4
Coverable lines:128
Total lines:289
Line coverage:96.8% (124 of 128)
Covered branches:0
Total branches:0
Covered methods:6
Total methods:6
Method coverage:100% (6 of 6)

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ECSRaycastSystem(...)0%110100%
Update()0%21.221092.31%
FindMatchingColliderEntity(...)0%330100%
CreateRay(...)0%880100%
CreateSDKRaycastHit(...)0%770100%
CreateRaycastLayerMask(...)0%440100%

File(s)

/tmp/workspace/unity-renderer/unity-renderer/Assets/DCLPlugins/ECS7/Systems/RaycastSystem/ECSRaycastSystem.cs

#LineLine coverage
 1using DCL;
 2using DCL.Configuration;
 3using DCL.Controllers;
 4using DCL.ECS7;
 5using DCL.ECS7.ComponentWrapper.Generic;
 6using DCL.ECS7.InternalComponents;
 7using DCL.ECSComponents;
 8using DCL.ECSComponents.Utils;
 9using DCL.ECSRuntime;
 10using DCL.Helpers;
 11using DCL.Models;
 12using System.Collections.Generic;
 13using UnityEngine;
 14using Ray = UnityEngine.Ray;
 15using RaycastHit = DCL.ECSComponents.RaycastHit;
 16using Vector3 = Decentraland.Common.Vector3;
 17
 18namespace ECSSystems.ECSRaycastSystem
 19{
 20    /// <summary>
 21    /// This system executes the needed raycasts on every entity that has a Raycast component and
 22    /// adds a RaycastResult component to the corresponding raycasting entities, complying with
 23    /// ADR-200: https://adr.decentraland.org/adr/ADR-200
 24    /// </summary>
 25    public class ECSRaycastSystem
 26    {
 27        private readonly IInternalECSComponent<InternalRaycast> internalRaycastComponent;
 28        private readonly IInternalECSComponent<InternalColliders> physicsColliderComponent;
 29        private readonly IInternalECSComponent<InternalColliders> onPointerColliderComponent;
 30        private readonly IInternalECSComponent<InternalColliders> customLayerColliderComponent;
 31        private readonly IInternalECSComponent<InternalEngineInfo> engineInfoComponent;
 32        private readonly IReadOnlyDictionary<int, ComponentWriter> componentsWriter;
 33        private readonly WrappedComponentPool<IWrappedComponent<PBRaycastResult>> raycastResultPool;
 34
 2935        public ECSRaycastSystem(
 36            IInternalECSComponent<InternalRaycast> internalRaycastComponent,
 37            IInternalECSComponent<InternalColliders> physicsColliderComponent,
 38            IInternalECSComponent<InternalColliders> onPointerColliderComponent,
 39            IInternalECSComponent<InternalColliders> customLayerColliderComponent,
 40            IInternalECSComponent<InternalEngineInfo> engineInfoComponent,
 41            IReadOnlyDictionary<int, ComponentWriter> componentsWriter,
 42            WrappedComponentPool<IWrappedComponent<PBRaycastResult>> raycastResultPool)
 43        {
 2944            this.internalRaycastComponent = internalRaycastComponent;
 2945            this.physicsColliderComponent = physicsColliderComponent;
 2946            this.onPointerColliderComponent = onPointerColliderComponent;
 2947            this.customLayerColliderComponent = customLayerColliderComponent;
 2948            this.engineInfoComponent = engineInfoComponent;
 2949            this.componentsWriter = componentsWriter;
 2950            this.raycastResultPool = raycastResultPool;
 2951        }
 52
 53        public void Update()
 54        {
 3155            var raycastComponentGroup = internalRaycastComponent.GetForAll();
 3156            int entitiesCount = raycastComponentGroup.Count;
 57
 58            // Note: the components are traversed backwards as some internal raycast components may be removed during it
 12259            for (int i = entitiesCount - 1; i >= 0 ; i--)
 60            {
 3061                PBRaycast model = raycastComponentGroup[i].value.model.raycastModel;
 3062                IDCLEntity entity = raycastComponentGroup[i].value.entity;
 3063                IParcelScene scene = raycastComponentGroup[i].value.scene;
 64
 65                // Wait until scene initial GLTFs are finished loading (handled at SceneStateHandler)
 3066                if (!scene.IsInitMessageDone()) continue;
 67
 3068                if (model.QueryType == RaycastQueryType.RqtNone)
 69                {
 070                    internalRaycastComponent.RemoveFor(scene, entity);
 071                    continue;
 72                }
 73
 3074                Ray ray = CreateRay(scene, entity, model);
 3075                if (ray.direction == UnityEngine.Vector3.zero)
 76                {
 077                    Debug.LogError("Raycast error: direction cannot be Vector3.Zero(); Raycast aborted.");
 078                    return;
 79                }
 80
 3081                var pooledRaycastResult = raycastResultPool.Get();
 3082                PBRaycastResult result = pooledRaycastResult.WrappedComponent.Model;
 3083                result.Direction = ProtoConvertUtils.UnityVectorToPBVector(ray.direction);
 3084                result.GlobalOrigin = ProtoConvertUtils.UnityVectorToPBVector(ray.origin);
 3085                result.Timestamp = model.Timestamp;
 3086                result.TickNumber = engineInfoComponent.GetFor(scene, SpecialEntityId.SCENE_ROOT_ENTITY).Value.model.Sce
 87
 88                // Hit everything by default except 'OnPointer' layer
 3089                int raycastUnityLayerMask = CreateRaycastLayerMask(model);
 90
 3091                UnityEngine.RaycastHit[] hits = null;
 92
 93                // RaycastAll is used because 'Default' layer represents a combination of ClPointer and ClPhysics
 94                // and 'SDKCustomLayer' layer represents 8 different SDK layers: ClCustom1~8
 3095                hits = Physics.RaycastAll(ray, model.MaxDistance, raycastUnityLayerMask);
 3096                if (hits != null)
 97                {
 3098                    RaycastHit closestHit = null;
 13299                    for (int j = 0; j < hits.Length; j++)
 100                    {
 36101                        UnityEngine.RaycastHit currentHit = hits[j];
 36102                        uint raycastSDKCollisionMask = model.GetCollisionMask();
 36103                        KeyValuePair<IDCLEntity, uint>? hitEntity = null;
 104
 36105                        if (LayerMaskUtils.IsInLayerMask(raycastSDKCollisionMask, (int)ColliderLayer.ClPhysics))
 19106                            hitEntity = FindMatchingColliderEntity(physicsColliderComponent.GetForAll(), currentHit.coll
 36107                        if(hitEntity == null && LayerMaskUtils.IsInLayerMask(raycastSDKCollisionMask, (int)ColliderLayer
 5108                            hitEntity = FindMatchingColliderEntity(onPointerColliderComponent.GetForAll(), currentHit.co
 36109                        if(hitEntity == null && LayerMaskUtils.LayerMaskHasAnySDKCustomLayer(raycastSDKCollisionMask))
 17110                            hitEntity = FindMatchingColliderEntity(customLayerColliderComponent.GetForAll(), currentHit.
 111
 36112                        var hit = CreateSDKRaycastHit(scene, model, currentHit, hitEntity, result.GlobalOrigin);
 36113                        if (hit == null) continue;
 114
 23115                        if (model.QueryType == RaycastQueryType.RqtHitFirst)
 116                        {
 117                            // Since Unity's RaycastAll() resulting collection order is random (not based on hit distanc
 118                            // the closest hit has to be identified and populate the final collection with only that one
 14119                            if (closestHit == null || currentHit.distance < closestHit.Length)
 7120                                closestHit = hit;
 121                        }
 122                        else
 123                        {
 9124                            result.Hits.Add(hit);
 125                        }
 126                    }
 127
 30128                    if (model.QueryType == RaycastQueryType.RqtHitFirst && closestHit != null)
 6129                        result.Hits.Add(closestHit);
 130                }
 131
 30132                if (componentsWriter.TryGetValue(scene.sceneData.sceneNumber, out var writer))
 133                {
 27134                    writer.Put(entity.entityId, ComponentID.RAYCAST_RESULT, pooledRaycastResult);
 135                }
 136
 137                // If the raycast on that entity isn't 'continuous' the internal component is removed and won't
 138                // be used for raycasting on the next system iteration. If its timestamp changes, the handler
 139                // adds the internal raycast component again.
 30140                if(!model.HasContinuous || !model.Continuous)
 27141                    internalRaycastComponent.RemoveFor(scene, entity);
 142            }
 31143        }
 144
 145        private KeyValuePair<IDCLEntity, uint>? FindMatchingColliderEntity(IReadOnlyList<KeyValueSetTriplet<IParcelScene
 146        {
 41147            int componentsCount = componentGroup.Count;
 174148            for (int i = 0; i < componentsCount; i++)
 149            {
 76150                var colliders = componentGroup[i].value.model.colliders;
 76151                if (colliders.ContainsKey(targetCollider))
 152                {
 30153                    return new KeyValuePair<IDCLEntity, uint>(componentGroup[i].value.entity, colliders[targetCollider])
 154                }
 155            }
 156
 11157            return null;
 158        }
 159
 160        private Ray CreateRay(IParcelScene scene, IDCLEntity entity, PBRaycast model)
 161        {
 30162            Transform entityTransform = entity.gameObject.transform;
 30163            UnityEngine.Vector3 sceneWorldPosition = Utils.GridToWorldPosition(scene.sceneData.basePosition.x, scene.sce
 30164            UnityEngine.Vector3 sceneUnityPosition = PositionUtils.WorldToUnityPosition(sceneWorldPosition);
 30165            UnityEngine.Vector3 rayOrigin = entityTransform.position + (model.OriginOffset != null ? ProtoConvertUtils.P
 166
 30167            UnityEngine.Vector3 rayDirection = UnityEngine.Vector3.forward;
 30168            switch (model.DirectionCase)
 169            {
 170                case PBRaycast.DirectionOneofCase.LocalDirection:
 171                    // The direction of the ray in local coordinates (relative to the origin point)
 5172                    UnityEngine.Vector3 localDirectionVector = entityTransform.rotation * ProtoConvertUtils.PBVectorToUn
 5173                    if(localDirectionVector != UnityEngine.Vector3.zero)
 4174                        rayDirection = localDirectionVector;
 4175                    break;
 176                case PBRaycast.DirectionOneofCase.GlobalTarget:
 177                    // Target position to cast the ray towards, in global coordinates
 3178                    UnityEngine.Vector3 directionTowardsGlobalTarget = sceneUnityPosition + ProtoConvertUtils.PBVectorTo
 3179                    if(directionTowardsGlobalTarget != UnityEngine.Vector3.zero)
 2180                        rayDirection = directionTowardsGlobalTarget;
 2181                    break;
 182                case PBRaycast.DirectionOneofCase.TargetEntity:
 183                    // Target entity to cast the ray towards
 2184                    if (scene.entities.TryGetValue(model.TargetEntity, out IDCLEntity targetEntity))
 185                    {
 1186                        rayDirection = targetEntity.gameObject.transform.position - entityTransform.position;
 187                    }
 188                    else
 189                    {
 1190                        UnityEngine.Vector3 directionTowardsSceneRootEntity = scene.GetSceneTransform().position - entit
 1191                        if (directionTowardsSceneRootEntity != UnityEngine.Vector3.zero)
 1192                            rayDirection = directionTowardsSceneRootEntity;
 193                    }
 1194                    break;
 195                case PBRaycast.DirectionOneofCase.GlobalDirection:
 196                    // The direction of the ray in global coordinates
 20197                    UnityEngine.Vector3 globalDirectionVector = ProtoConvertUtils.PBVectorToUnityVector(model.GlobalDire
 20198                    if(globalDirectionVector != UnityEngine.Vector3.zero)
 19199                        rayDirection = globalDirectionVector;
 200                    break;
 201            }
 202
 30203            Ray ray = new Ray
 204            {
 205                origin = rayOrigin,
 206                direction = rayDirection.normalized
 207            };
 30208            return ray;
 209        }
 210
 211        private RaycastHit CreateSDKRaycastHit(IParcelScene scene, PBRaycast model, UnityEngine.RaycastHit unityRaycastH
 212        {
 36213            RaycastHit hit = null;
 36214            uint modelCollisionLayerMask = model.GetCollisionMask();
 215
 36216            if (hitEntity != null) // SDK7 entity, otherwise the ray hit an SDK6 entity
 217            {
 218                // TODO: figure out how we can cache or pool this hit instance to reduce allocations.
 219                // since is part of a protobuf message it life span is uncertain, it could be disposed
 220                // after message is sent to the scene or dropped for a new message
 30221                IDCLEntity entity = hitEntity.Value.Key;
 30222                uint collisionMask = hitEntity.Value.Value;
 223
 224                // hitEntity has to be evaluated since 'Default' layer represents a combination of ClPointer
 225                // and ClPhysics, and 'SDKCustomLayer' layer represents 8 different SDK layers: ClCustom1~8
 30226                if ((modelCollisionLayerMask & collisionMask) == 0)
 7227                    return null;
 228
 23229                hit = new RaycastHit();
 23230                hit.EntityId = (uint)entity.entityId;
 231            }
 6232            else if ((!scene.isPersistent && !scene.isPortableExperience)
 233                     || (modelCollisionLayerMask & (int)ColliderLayer.ClPhysics) == 0) // 'Physics' layer for non-sdk7 c
 234            {
 6235                return null;
 236            }
 237
 23238            hit ??= new RaycastHit();
 23239            hit.MeshName = unityRaycastHit.collider.name;
 23240            hit.Length = unityRaycastHit.distance;
 23241            hit.GlobalOrigin = globalOrigin;
 242
 23243            var worldPosition = WorldStateUtils.ConvertUnityToScenePosition(unityRaycastHit.point, scene);
 23244            hit.Position = new Vector3();
 23245            hit.Position.X = worldPosition.x;
 23246            hit.Position.Y = worldPosition.y;
 23247            hit.Position.Z = worldPosition.z;
 248
 23249            hit.NormalHit = new Vector3();
 23250            hit.NormalHit.X = unityRaycastHit.normal.x;
 23251            hit.NormalHit.Y = unityRaycastHit.normal.y;
 23252            hit.NormalHit.Z = unityRaycastHit.normal.z;
 253
 23254            return hit;
 255        }
 256
 257        private int CreateRaycastLayerMask(PBRaycast model)
 258        {
 30259            uint sdkLayerMask = 0;
 30260            int unityLayerMask = (1 << PhysicsLayers.characterLayer);
 30261            sdkLayerMask = model.GetCollisionMask();
 262
 30263            if (sdkLayerMask == (int)ColliderLayer.ClPointer)
 264            {
 2265                unityLayerMask |= (1 << PhysicsLayers.onPointerEventLayer);
 2266                unityLayerMask |= (1 << PhysicsLayers.defaultLayer); // Pointer + Physics ends up in 'Default' GO Layer
 2267                return unityLayerMask;
 268            }
 269
 28270            if (sdkLayerMask == (int)ColliderLayer.ClPhysics)
 271            {
 24272                unityLayerMask |= (1 << PhysicsLayers.characterOnlyLayer);
 24273                unityLayerMask |= (1 << PhysicsLayers.defaultLayer); // Pointer + Physics ends up in 'Default' GO Layer
 24274                return unityLayerMask;
 275            }
 276
 4277            unityLayerMask |= (1 << PhysicsLayers.defaultLayer)
 278                              | (1 << PhysicsLayers.characterOnlyLayer)
 279                              | (1 << PhysicsLayers.onPointerEventLayer);
 280
 4281            if (LayerMaskUtils.LayerMaskHasAnySDKCustomLayer(sdkLayerMask))
 282            {
 4283                unityLayerMask |= (1 << PhysicsLayers.sdkCustomLayer);
 284            }
 285
 4286            return unityLayerMask;
 287        }
 288    }
 289}