void CleanupMultithreadRaycasts()
        {
#if UNITY_2018_1_OR_NEWER
            if (_multithreadRaycasts == null)
            {
                return;
            }

            _multithreadRaycasts.raycasts.Dispose();
            _multithreadRaycasts.hits.Dispose();
            _multithreadRaycasts = null;
#endif
        }
        bool CalculateLineOfSight3D(Vector3 eye, float radius, float penetration, LayerMask layermask, Vector3 up, Vector3 forward)
        {
            bool  hashit = false;
            float angle  = 360.0f / _distances.Length;

#if UNITY_2018_1_OR_NEWER
            if (multithreading)
            {
                // make sure native arrays are ready
                if (_multithreadRaycasts == null)
                {
                    _multithreadRaycasts = new FogOfWarUnitRaycasts()
                    {
                        raycasts = new NativeArray <RaycastCommand>(lineOfSightRaycastCount, Allocator.Persistent),
                        hits     = new NativeArray <RaycastHit>(lineOfSightRaycastCount, Allocator.Persistent)
                    };
                }
                else if (_multithreadRaycasts.raycasts.Length != lineOfSightRaycastCount)
                {
                    _multithreadRaycasts.raycasts = new NativeArray <RaycastCommand>(lineOfSightRaycastCount, Allocator.Persistent);
                    _multithreadRaycasts.hits     = new NativeArray <RaycastHit>(lineOfSightRaycastCount, Allocator.Persistent);
                }

                // prepare raycasts
                for (int i = 0; i < _distances.Length; ++i)
                {
                    Vector3 dir = Quaternion.AngleAxis(angle * i, up) * forward;
                    _multithreadRaycasts.raycasts[i] = new RaycastCommand(eye, dir, radius, layermask);
                }

                // perform raycasts
                RaycastCommand.ScheduleBatch(_multithreadRaycasts.raycasts, _multithreadRaycasts.hits, 1).Complete();

                // copy results
                for (int i = 0; i < _distances.Length; ++i)
                {
                    if (_multithreadRaycasts.hits[i].collider != null)
                    {
                        _distances[i] = (_multithreadRaycasts.hits[i].distance + penetration) / radius;
                        if (_distances[i] < 1)
                        {
                            hashit = true;
                        }
                        else
                        {
                            _distances[i] = 1;
                        }
                    }
                    else
                    {
                        _distances[i] = 1;
                    }
                }
            }
            else
#endif
            {
                CleanupMultithreadRaycasts();
                for (int i = 0; i < _distances.Length; ++i)
                {
                    Vector3 dir = Quaternion.AngleAxis(angle * i, up) * forward;
                    if (Physics.Raycast(eye, dir, out RaycastHit hit, radius, layermask))
                    {
                        _distances[i] = (hit.distance + penetration) / radius;
                        if (_distances[i] < 1)
                        {
                            hashit = true;
                        }
                        else
                        {
                            _distances[i] = 1;
                        }
                    }