/// <summary>
        /// Updates the shadow transformation and state.
        /// </summary>
        /// <param name="frustumPlanes">
        /// The frustum planes used for culling the shadow.
        /// </param>
        /// <param name="force">
        /// Whether to force the update, even if it is not needed.
        /// </param>
        /// <returns>
        /// The <see cref="RecalculateShadowResult"/> indicating the
        /// shadow behaviour change during calculation.
        /// </returns>
        public RecalculateShadowResult RecalculateShadow(Plane[] frustumPlanes, bool force)
        {
#if UNITY_EDITOR
            if (_isDestroyed)
            {
                return(RecalculateShadowResult.Skipped);
            }
#endif
            _isVisible = _isStatic;

            // Determine whether the owner GameObject changed the active state
            // and react correspondingly
            bool isGameObjectActive = gameObject.activeInHierarchy;
            if (isGameObjectActive != _isGameObjectActivePrev)
            {
                _isGameObjectActivePrev = isGameObjectActive;
                if (isGameObjectActive)
                {
                    RegisterShadow();
                    return(RecalculateShadowResult.ChangedManager);
                }

                UnregisterShadow();
                return(RecalculateShadowResult.ChangedManager);
            }

            _isGameObjectActivePrev = isGameObjectActive;
            if (!isGameObjectActive)
            {
                return(RecalculateShadowResult.Skipped);
            }

            // Updating the transform state (position and forward vectors)
            // Determine whether the transform has moved
            Vector3    transformPosition = _transform.position;
            Quaternion transformRotation = _transform.rotation;
            Vector3    transformForward  = transformRotation * kVector3Forward;

            bool transformChanged = false;
            if (_autoStaticTime > 0f)
            {
                if (transformPosition.x != _transformPositionPrev.x ||
                    transformPosition.y != _transformPositionPrev.y ||
                    transformPosition.z != _transformPositionPrev.z ||
                    transformForward.x != _transformForwardPrev.x ||
                    transformForward.y != _transformForwardPrev.y ||
                    transformForward.z != _transformForwardPrev.z
                    )
                {
                    _autoStaticTimeCounter = 0f;
                    transformChanged       = true;
                }

                _transformPositionPrev = transformPosition;
                _transformForwardPrev  = transformForward;
            }

            float deltaTime = Time.deltaTime;

            if (!_isFirstCalculation)
            {
                // If we have AutoStatic
#if UNITY_EDITOR
                if (Application.isPlaying)
                {
#endif
                if (_autoStaticTime > 0f)
                {
                    // If the object has moved - remove the shadow
                    // from static manager and move to non-static
                    if (_isStatic && transformChanged)
                    {
                        UnregisterShadow();
                        _isStatic = false;
                        RegisterShadow();
                        return(RecalculateShadowResult.ChangedManager);
                    }

                    // If the object hasn't moved for AutoStaticTime seconds,
                    // then mark it as static
                    _autoStaticTimeCounter += deltaTime;
                    if (!_isStatic && _autoStaticTimeCounter > _autoStaticTime)
                    {
                        UnregisterShadow();
                        _isStatic = true;
                        RegisterShadow();
                        return(RecalculateShadowResult.ChangedManager);
                    }
                }

                // Do not update static shadows by default
                if (_isStatic && !force)
                {
                    return(RecalculateShadowResult.Skipped);
                }
#if UNITY_EDITOR
            }
#endif
            }

            // Is this our first update?
            _isFirstCalculation = false;

            // Determine the light source position
            bool useLightSource = _lightVectorSource == LightVectorSourceEnum.GameObject && _lightSourceObject != null;
            Vector3 lightSourcePosition;
            if (useLightSource)
            {
                lightSourcePosition = _lightSourceObject.position;
            }
            else
            {
                lightSourcePosition.x = 0f;
                lightSourcePosition.y = 0f;
                lightSourcePosition.z = 0f;
            }

            // The actual light direction vector that'll be used
            Vector3 actualLightVector;
            if (useLightSource)
            {
                if (_lightSourceObjectIsDirectionalLight)
                {
                    actualLightVector = _lightSourceObject.rotation * kVector3Forward;
                }
                else
                {
                    actualLightVector.x = transformPosition.x - lightSourcePosition.x;
                    actualLightVector.y = transformPosition.y - lightSourcePosition.y;
                    actualLightVector.z = transformPosition.z - lightSourcePosition.z;
                    actualLightVector   = actualLightVector.FastNormalized();
                }
            }
            else
            {
                actualLightVector = _lightVector;
            }

            _actualLightVectorPrev = actualLightVector;

            // Do a raycast from transform.position to the center of the shadow
            RaycastHit hitInfo;
            bool raycastResult = Physics.Raycast(transformPosition, actualLightVector, out hitInfo, _projectionDistance, _layerMaskInt);

            if (raycastResult)
            {
                Vector3 hitPoint = hitInfo.point;

                // Scale the shadow respectively
                Vector3 lossyScale             = _transform.lossyScale;
                float   scaledDoubleShadowSize = Mathf.Max(Mathf.Max(lossyScale.x, lossyScale.y), lossyScale.z) * _shadowSize;

                // Distance scale
                float distance    = hitInfo.distance;
                float scaleWeight = (distance - _shadowSizeScaleStartDistance) / (_shadowSizeScaleEndDistance - _shadowSizeScaleStartDistance);
                scaleWeight = scaleWeight > 1f ? 1f : (scaleWeight < 0f ? 0f : scaleWeight);
                if (scaleWeight != 0f)
                {
                    float scale = _shadowSizeEndScale + (1f - _shadowSizeEndScale) * (1f - scaleWeight);
                    scaledDoubleShadowSize *= scale;
                }

                float scaledShadowSize = scaledDoubleShadowSize * 0.5f;
                if (!_isStatic && _cullInvisible)
                {
                    // We can calculate approximate bounds for orthographic shadows easily
                    // and cull shadows based on these bounds and camera frustum
                    if (!_isPerspectiveProjection)
                    {
                        Vector3 shadowScale;
                        shadowScale.x = scaledShadowSize;
                        shadowScale.y = scaledShadowSize;
                        shadowScale.z = scaledShadowSize;
                        Bounds bounds = new Bounds();
                        bounds.center  = hitPoint;
                        bounds.extents = shadowScale;
                        _isVisible     = GeometryUtility.TestPlanesAABB(frustumPlanes, bounds);
                        if (!_isVisible)
                        {
                            return(RecalculateShadowResult.Skipped);
                        }
                    }
                    else
                    {
                        // For perspective shadows, we can at least try to
                        // not draw shadows that fall on invisible objects
                        Bounds rendererBounds;
                        bool   result = ShadowManager.Instance.ColliderToRendererBoundsCache.GetRendererBoundsFromCollider(hitInfo.collider, out rendererBounds);
                        if (result)
                        {
                            _isVisible = GeometryUtility.TestPlanesAABB(frustumPlanes, rendererBounds);
                            if (!_isVisible)
                            {
                                return(RecalculateShadowResult.Skipped);
                            }
                        }
                    }
                }

                // Calculate angle from light direction vector to surface normal
                if (_isSmoothRotation)
                {
                    _normal = Vector3.Lerp(_normal, hitInfo.normal, _smoothRotationSpeed * deltaTime).FastNormalized();
                }
                else
                {
                    _normal = hitInfo.normal;
                }

                float lightVectorToNormalDot = -actualLightVector.x * _normal.x - actualLightVector.y * _normal.y - actualLightVector.z * _normal.z;
                float angleToNormal          = FastMath.FastPseudoAcos(lightVectorToNormalDot) * Mathf.Rad2Deg;
                if (angleToNormal > _angleFadeMax)
                {
                    // Skip shadows that fall with extreme angles
                    _isVisible = false;
                    return(RecalculateShadowResult.Skipped);
                }

                // Determine the forward direction of shadow base quad
                Vector3 forward;
                float   dot = transformForward.x * actualLightVector.x + transformForward.y * actualLightVector.y + transformForward.z * actualLightVector.z;
                if (Mathf.Abs(dot) < 1f - Vector3.kEpsilon)
                {
                    forward.x = transformForward.x - dot * actualLightVector.x;
                    forward.y = transformForward.y - dot * actualLightVector.y;
                    forward.z = transformForward.z - dot * actualLightVector.z;
                    forward   = forward.FastNormalized();
                }
                else
                {
                    // If the forward direction matches the light direction vector somehow
                    Vector3 transformUp = transformRotation * kVector3Up;
                    dot       = transformUp.x * actualLightVector.x + transformUp.y * actualLightVector.y + transformUp.z * actualLightVector.z;
                    forward.x = transformUp.x - dot * actualLightVector.x;
                    forward.y = transformUp.y - dot * actualLightVector.y;
                    forward.z = transformUp.z - dot * actualLightVector.z;
                    forward   = forward.FastNormalized();
                }

                // Rotation of shadow base quad
                Vector3 actualLightVectorNegated;
                actualLightVectorNegated.x = -actualLightVector.x;
                actualLightVectorNegated.y = -actualLightVector.y;
                actualLightVectorNegated.z = -actualLightVector.z;
                Quaternion rotation = Quaternion.LookRotation(forward, actualLightVectorNegated);

                // Optimized version of
                // Vector3 right = rotation * Vector3.right;
                float   num2  = rotation.y * 2f;
                float   num3  = rotation.z * 2f;
                float   num5  = rotation.y * num2;
                float   num6  = rotation.z * num3;
                float   num7  = rotation.x * num2;
                float   num8  = rotation.x * num3;
                float   num11 = rotation.w * num2;
                float   num12 = rotation.w * num3;
                Vector3 right;
                right.x = 1f - (num5 + num6);
                right.y = num7 + num12;
                right.z = num8 - num11;

                // Base vertices calculation
                float aspectRatioRec = 1f / _aspectRatio;

                Vector3 tmp1;
                tmp1.x = forward.x * scaledShadowSize;
                tmp1.y = forward.y * scaledShadowSize;
                tmp1.z = forward.z * scaledShadowSize;
                Vector3 tmp2;
                tmp2.x = right.x * aspectRatioRec * scaledShadowSize;
                tmp2.y = right.y * aspectRatioRec * scaledShadowSize;
                tmp2.z = right.z * aspectRatioRec * scaledShadowSize;

                Vector3 diff;
                diff.x = tmp1.x - tmp2.x;
                diff.y = tmp1.y - tmp2.y;
                diff.z = tmp1.z - tmp2.z;
                Vector3 sum;
                sum.x = tmp1.x + tmp2.x;
                sum.y = tmp1.y + tmp2.y;
                sum.z = tmp1.z + tmp2.z;

                Vector3 baseVertex;
                baseVertex.x     = transformPosition.x - sum.x;
                baseVertex.y     = transformPosition.y - sum.y;
                baseVertex.z     = transformPosition.z - sum.z;
                _baseVertices[0] = baseVertex;

                baseVertex.x     = transformPosition.x + diff.x;
                baseVertex.y     = transformPosition.y + diff.y;
                baseVertex.z     = transformPosition.z + diff.z;
                _baseVertices[1] = baseVertex;

                baseVertex.x     = transformPosition.x + sum.x;
                baseVertex.y     = transformPosition.y + sum.y;
                baseVertex.z     = transformPosition.z + sum.z;
                _baseVertices[2] = baseVertex;

                baseVertex.x     = transformPosition.x - diff.x;
                baseVertex.y     = transformPosition.y - diff.y;
                baseVertex.z     = transformPosition.z - diff.z;
                _baseVertices[3] = baseVertex;

                // Calculate a plane from normal and position
                Vector3 offsetPoint;
                offsetPoint.x = hitPoint.x + _normal.x * _shadowOffset;
                offsetPoint.y = hitPoint.y + _normal.y * _shadowOffset;
                offsetPoint.z = hitPoint.z + _normal.z * _shadowOffset;
                FastMath.Plane shadowPlane = new FastMath.Plane(_normal, offsetPoint);

                float        distanceToPlane;
                FastMath.Ray ray = new FastMath.Ray();

                // Calculate the shadow vertices
                if (_isPerspectiveProjection && useLightSource)
                {
                    ray.Direction.x = lightSourcePosition.x - _baseVertices[0].x;
                    ray.Direction.y = lightSourcePosition.y - _baseVertices[0].y;
                    ray.Direction.z = lightSourcePosition.z - _baseVertices[0].z;
                    ray.Origin      = _baseVertices[0];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[0].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[0].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[0].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Direction.x = lightSourcePosition.x - _baseVertices[1].x;
                    ray.Direction.y = lightSourcePosition.y - _baseVertices[1].y;
                    ray.Direction.z = lightSourcePosition.z - _baseVertices[1].z;
                    ray.Origin      = _baseVertices[1];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[1].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[1].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[1].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Direction.x = lightSourcePosition.x - _baseVertices[2].x;
                    ray.Direction.y = lightSourcePosition.y - _baseVertices[2].y;
                    ray.Direction.z = lightSourcePosition.z - _baseVertices[2].z;
                    ray.Origin      = _baseVertices[2];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[2].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[2].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[2].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Direction.x = lightSourcePosition.x - _baseVertices[3].x;
                    ray.Direction.y = lightSourcePosition.y - _baseVertices[3].y;
                    ray.Direction.z = lightSourcePosition.z - _baseVertices[3].z;
                    ray.Origin      = _baseVertices[3];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[3].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[3].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[3].z = ray.Origin.z + ray.Direction.z * distanceToPlane;
                }
                else
                {
                    ray.Direction = actualLightVector;

                    ray.Origin = _baseVertices[0];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[0].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[0].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[0].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Origin = _baseVertices[1];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[1].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[1].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[1].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Origin = _baseVertices[2];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[2].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[2].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[2].z = ray.Origin.z + ray.Direction.z * distanceToPlane;

                    ray.Origin = _baseVertices[3];
                    shadowPlane.Raycast(ref ray, out distanceToPlane);
                    _shadowVertices[3].x = ray.Origin.x + ray.Direction.x * distanceToPlane;
                    _shadowVertices[3].y = ray.Origin.y + ray.Direction.y * distanceToPlane;
                    _shadowVertices[3].z = ray.Origin.z + ray.Direction.z * distanceToPlane;
                }

                // Calculate the shadow alpha
                float shadowAlpha = _initialAlpha;

                // Alpha base on distance to the surface
                if (distance > _fadeDistance)
                {
                    shadowAlpha = shadowAlpha - (distance - _fadeDistance) / (_projectionDistance - _fadeDistance) * shadowAlpha;
                }

                // Alpha based on shadow fall angle
                if (angleToNormal > _angleFadeMin)
                {
                    shadowAlpha = shadowAlpha - (angleToNormal - _angleFadeMin) / (_angleFadeMax - _angleFadeMin) * shadowAlpha;
                }

                // Convert float alpha to byte
                //_color.a = shadowAlpha;
                _color32.a = (byte)(shadowAlpha * 255f);

                _isVisible = true;
            }

            return(RecalculateShadowResult.Recalculated);
        }