Graphics Programming

Vision Feedback – Line of Sight Algorithm inside Unity3D (Mid 2020)

One of my main tasks during my time in Hidden People Club, was constructing a vision cone for the guards. These characters were meant to chase the player if they entered the pink zone, and arrest them when they get into the red zone. Besides that, the player was able to hide behind walls, so the mesh should reflect that. The best way of doing it was cropping the mesh when it hit walls and objects.

The mesh was built using a line of sight algorithm, which involves shooting ray cast all over a cone of a fixed angle defined by the game designer, and ask if the ray hits an object, and if that object was an obstacle. The edges of the object was a case treated with special conditions, so it can project the rays in a right way.

private void CreateVisibilityMesh()
        {
            Vector3 dir = initialRotation * mViewer.GameObject.transform.forward;
            Vector3 pos = mViewer.GameObject.transform.position;

            List<Vector3> viewPoints = new List<Vector3>();  
            List<Vector3> borderPoints = new List<Vector3>();

            Vector3 oldHitPoint = Vector3.zero;
            Collider oldHitCollider = null;

            RaycastHit raycastHit;

            for (int i = 0; i <= MeshSubdivision * 2; ++i)
            {   
                float angle = Vector3.SignedAngle(initialDir, dir, transform.up);
                bool inCone = angle <= mViewer.Angle && angle >= 0;

                float range = inCone ? mViewer.SoftSeenRange : ViewerCenterRadius;
                
                // Vision Cone Creation
                bool hit = Physics.Raycast(
                    pos, 
                    dir, 
                    out raycastHit, 
                    range, 
                    VisibilitySystem.Instance.BlockerLayers,
                    QueryTriggerInteraction.Ignore);

                if(i > 0 && inCone)
                {
                    if (oldHitCollider != raycastHit.collider)
                    {
                        Vector3 newHitPoint = hit ? raycastHit.point : pos + dir * range;
                        FindEdege(oldHitPoint, oldHitCollider, newHitPoint);
                        if(mEdgeFindResult[0] != Vector3.zero)
                        {
                            viewPoints.Add(mEdgeFindResult[0]);
                        }
                        if(mEdgeFindResult[1] != Vector3.zero)
                        {
                            viewPoints.Add(mEdgeFindResult[1]);
                        }
                    }   
                }
                
                Vector3 endPoint = hit ? raycastHit.point : pos + dir * range;

                if(angle == 0)
                {
                    viewPoints.Add(pos + dir * ViewerCenterRadius);
                }

                viewPoints.Add(endPoint);

                if(angle == mViewer.Angle)
                {
                    viewPoints.Add(pos + dir * ViewerCenterRadius);
                }

                oldHitPoint = endPoint;
                oldHitCollider = raycastHit.collider;

                dir = inCone ? coneRotationStep * dir : circleRotationStep * dir;
            }
            
            Vector3 endDir = Quaternion.Euler(0, mViewer.Angle/2, 0) * mViewer.GameObject.transform.forward ;
            int vertexCount = viewPoints.Count + 1;
            Vector3[] vertices = new Vector3[vertexCount];
            vertices[0] = Vector3.zero;
            Vector2[] uvs = new Vector2[vertexCount];
            uvs[0] = Vector2.zero;

            int[] triangles = new int[vertexCount * 3];
            
            for(int i = 0; i < viewPoints.Count ; i++)
            {
                vertices[i + 1] = transform.InverseTransformPoint(viewPoints[i]);

                // using distance from pos to viewPoint to determinate the v texture coord
                uvs[i + 1] = new Vector2(i/2*MeshSubdivision, (viewPoints[i] - pos).magnitude/mViewer.SoftSeenRange); 

                // -2 avoids to acces vertex that doesnt exists, cause we need i + 2 vertices to make a tri
                if (i < vertexCount - 2)
                {
                    triangles[i * 3] = 0;
                    triangles[i * 3 + 1] = 1 + i;
                    triangles[i * 3 + 2] = 2 + i;
                }
                else
                {
                    triangles[i * 3] = 0;
                    triangles[i * 3 + 1] = 1 + i;
                    triangles[i * 3 + 2] = 1;
                }

            }

            mViewMesh.Clear();
            mViewMesh.vertices = vertices;
            mViewMesh.triangles = triangles;
            mViewMesh.uv = uvs;
            mViewMesh.RecalculateNormals();

            Renderer.GetComponent<MeshFilter>().mesh = mViewMesh;

            float hardSeenLimit = mViewer.HardSeenRange / mViewer.SoftSeenRange;

            mMaterial.SetFloat("_HardSeenLimit", hardSeenLimit);
            Renderer.GetComponent<MeshRenderer>().material = mMaterial;

        }