Car/Assets/VolumetricLightBeam/Scripts/SD/VolumetricLightBeamSD.cs

1104 lines
45 KiB
C#
Raw Permalink Normal View History

//#define DEBUG_SHOW_APEX
//#define DEBUG_GLOBAL_VECTORS
//#define DEBUG_GLOBAL_RAYCAST_VECTORS
#if UNITY_2019_3_OR_NEWER
#define VLB_LIGHT_TEMPERATURE_SUPPORT
#endif
using UnityEngine;
using UnityEngine.Serialization;
using System.Collections;
namespace VLB
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
[SelectionBase]
[HelpURL(Consts.Help.SD.UrlBeam)]
public partial class VolumetricLightBeamSD : VolumetricLightBeamAbstractBase
{
public new const string ClassName = "VolumetricLightBeamSD";
/// <summary>
/// Get the color value from the light (when attached to a Spotlight) or not
/// </summary>
public bool colorFromLight = true;
/// <summary>
/// Apply a flat/plain/single color, or a gradient
/// </summary>
public ColorMode colorMode = Consts.Beam.ColorModeDefault;
public ColorMode usedColorMode
{
get
{
if (Config.Instance.featureEnabledColorGradient == FeatureEnabledColorGradient.Off) return ColorMode.Flat;
return colorMode;
}
}
/// <summary>
/// RGBA plain color, if colorMode is Flat (takes account of the alpha value).
/// </summary>
#if UNITY_2018_1_OR_NEWER
[ColorUsageAttribute(false, true)]
#else
[ColorUsageAttribute(false, true, 0f, 8f, 0.125f, 3f)]
#endif
[FormerlySerializedAs("colorValue")]
public Color color = Consts.Beam.FlatColor;
/// <summary>
/// Gradient color applied along the beam, if colorMode is Gradient (takes account of the color and alpha variations).
/// </summary>
public Gradient colorGradient;
#if UNITY_EDITOR
public override Color ComputeColorAtDepth(float depthRatio)
{
if (usedColorMode == ColorMode.Flat) return color;
else return colorGradient.Evaluate(depthRatio);
}
#endif
bool useColorFromAttachedLightSpot { get { return colorFromLight && lightSpotAttached != null; } }
bool useColorTemperatureFromAttachedLightSpot
{
get
{
#if VLB_LIGHT_TEMPERATURE_SUPPORT
return useColorFromAttachedLightSpot && lightSpotAttached.useColorTemperature && Config.Instance.useLightColorTemperature;
#else
return false;
#endif
}
}
/// <summary>
/// Get the intensity value from the light (when attached to a Spotlight) or not
/// </summary>
public bool intensityFromLight = true;
/// <summary>
/// Disabled: the inside and outside intensity values are the same and controlled by intensityGlobal property
/// Enabled: the inside and outside intensity values are distinct (intensityInside and intensityOutside)
/// </summary>
public bool intensityModeAdvanced = false;
/// <summary>
/// Beam inside intensity (when looking at the beam from the inside directly at the source).
/// You can change this property only if intensityModeAdvanced is true. Use intensityGlobal otherwise.
/// </summary>
[FormerlySerializedAs("alphaInside")]
[Min(Consts.Beam.IntensityMin)]
public float intensityInside = Consts.Beam.IntensityDefault;
[System.Obsolete("Use 'intensityGlobal' or 'intensityInside' instead")]
public float alphaInside { get { return intensityInside; } set { intensityInside = value; } }
/// <summary>
/// Beam outside intensity (when looking at the beam from behind).
/// You can change this property only if intensityModeAdvanced is true. Use intensityGlobal otherwise.
/// </summary>
[FormerlySerializedAs("alphaOutside"), FormerlySerializedAs("alpha")]
[Min(Consts.Beam.IntensityMin)]
public float intensityOutside = Consts.Beam.IntensityDefault;
[System.Obsolete("Use 'intensityGlobal' or 'intensityOutside' instead")]
public float alphaOutside { get { return intensityOutside; } set { intensityOutside = value; } }
/// <summary>
/// Global beam intensity, to use when intensityModeAdvanced is false.
/// Otherwise use intensityOutside and intensityInside independently.
/// </summary>
public float intensityGlobal { get { return intensityOutside; } set { intensityInside = value; intensityOutside = value; } }
/// <summary>
/// Multiplier to modulate the spotlight intensity.
/// </summary>
[Min(Consts.Beam.MultiplierMin)]
public float intensityMultiplier = Consts.Beam.MultiplierDefault;
public bool useIntensityFromAttachedLightSpot { get { return intensityFromLight && lightSpotAttached != null; } }
public void GetInsideAndOutsideIntensity(out float inside, out float outside)
{
if(intensityModeAdvanced)
{
inside = intensityInside;
outside = intensityOutside;
}
else
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
Debug.Assert(Utils.Approximately(intensityInside, intensityOutside), "The beam is not using advanced intensity mode, but its inside and outside have not the same value.", gameObject);
}
inside = outside = intensityOutside;
}
}
/// <summary>
/// HDRP Only
/// Use this property to set how much effect the camera exposure has on the beam intensity.
/// </summary>
[Range(Consts.Beam.HDRPExposureWeightMin, Consts.Beam.HDRPExposureWeightMax)]
public float hdrpExposureWeight = Consts.Beam.HDRPExposureWeightDefault;
/// <summary>
/// Change how the light beam colors will be mixed with the scene
/// </summary>
public BlendingMode blendingMode = Consts.Beam.BlendingModeDefault;
/// <summary>
/// Get the spotAngle value from the light (when attached to a Spotlight) or not
/// </summary>
[FormerlySerializedAs("angleFromLight")]
public bool spotAngleFromLight = true;
public bool useSpotAngleFromAttachedLightSpot { get { return spotAngleFromLight && lightSpotAttached != null; } }
/// <summary>
/// Spot Angle (in degrees). This doesn't take account of the radiusStart, and is not necessarily the same than the cone angle.
/// </summary>
[Range(Consts.Beam.SpotAngleMin, Consts.Beam.SpotAngleMax)]
public float spotAngle = Consts.Beam.SpotAngleDefault;
/// <summary>
/// Multiplier to modulate the spotlight spot angle.
/// </summary>
[Min(Consts.Beam.MultiplierMin)]
public float spotAngleMultiplier = Consts.Beam.MultiplierDefault;
/// <summary>
/// Cone Angle (in degrees). This takes account of the radiusStart, and is not necessarily the same than the spot angle.
/// </summary>
public float coneAngle { get { return Mathf.Atan2(coneRadiusEnd - coneRadiusStart, maxGeometryDistance) * Mathf.Rad2Deg * 2f; } }
/// <summary>
/// Start radius of the cone geometry.
/// 0 will generate a perfect cone geometry. Higher values will generate truncated cones.
/// </summary>
[FormerlySerializedAs("radiusStart")]
public float coneRadiusStart = Consts.Beam.ConeRadiusStart;
/// <summary>
/// End radius of the cone geometry
/// </summary>
public float coneRadiusEnd {
get { return Utils.ComputeConeRadiusEnd(maxGeometryDistance, spotAngle); }
set { spotAngle = Utils.ComputeSpotAngle(maxGeometryDistance, value); }
}
/// <summary>
/// Volume (in unit^3) of the cone (from the base to fallOffEnd)
/// </summary>
public float coneVolume { get { float r1 = coneRadiusStart, r2 = coneRadiusEnd; return (Mathf.PI / 3) * (r1 * r1 + r1 * r2 + r2 * r2) * fallOffEnd; } }
/// <summary>
/// Apex distance of the truncated radius
/// If coneRadiusStart = 0, the apex is the at the truncated radius, so coneApexOffsetZ = 0
/// Otherwise, coneApexOffsetZ > 0 and represents the local position Z offset
/// </summary>
public float coneApexOffsetZ {
get { // simple intercept
float ratioRadius = coneRadiusStart / coneRadiusEnd;
return ratioRadius == 1f ? float.MaxValue : ((maxGeometryDistance * ratioRadius) / (1 - ratioRadius));
}
}
public Vector3 coneApexPositionLocal { get { return new Vector3(0, 0, -coneApexOffsetZ); } }
public Vector3 coneApexPositionGlobal { get { return transform.localToWorldMatrix.MultiplyPoint(coneApexPositionLocal); } }
public override bool IsScalable() { return true; }
/// <summary>
/// - Fast: a lot of computation are done on the vertex shader to maximize performance.
/// - High: most of the computation are done on the pixel shader to maximize graphical quality at some performance cost.
/// </summary>
public ShaderAccuracy shaderAccuracy = Consts.Beam.ShaderAccuracyDefault;
/// <summary>
/// Shared: this beam will use the global shared mesh (recommended setting, since it will save a lot on memory).
/// Custom: this beam will use a custom mesh instead. Check the following properties to control how the mesh will be generated.
/// </summary>
public MeshType geomMeshType = Consts.Beam.GeomMeshType;
/// <summary>
/// Set a custom number of Sides for the cone geometry.
/// Higher values give better looking results, but require more memory and graphic performance.
/// This value is only used when geomMeshType is Custom.
/// </summary>
[FormerlySerializedAs("geomSides")]
public int geomCustomSides = Consts.Beam.GeomSidesDefault;
/// <summary>
/// Returns the effective number of Sides used by this beam.
/// Could come from the shared mesh, or the custom mesh
/// </summary>
public int geomSides
{
get { return geomMeshType == MeshType.Custom ? geomCustomSides : Config.Instance.sharedMeshSides; }
set { geomCustomSides = value; Debug.LogWarningFormat("The setter VLB.{0}.geomSides is OBSOLETE and has been renamed to geomCustomSides.", ClassName); }
}
/// <summary>
/// Set a custom Segments for the cone geometry.
/// Higher values give better looking results, but require more memory and graphic performance.
/// This value is only used when geomMeshType is Custom.
/// </summary>
public int geomCustomSegments = Consts.Beam.GeomSegmentsDefault;
/// <summary>
/// Returns the effective number of Segments used by this beam.
/// Could come from the shared mesh, or the custom mesh
/// </summary>
public int geomSegments
{
get { return geomMeshType == MeshType.Custom ? geomCustomSegments : Config.Instance.sharedMeshSegments; }
set { geomCustomSegments = value; Debug.LogWarningFormat("The setter VLB.{0}.geomSegments is OBSOLETE and has been renamed to geomCustomSegments.", ClassName); }
}
/// <summary>
/// Distort the beam's shape horizontally (X axis) and vertically (Y axis) while keeping its circular slice and gradient unchanged.
/// (This property is only available when 'Mesh Type' is 'Custom')
/// </summary>
public Vector3 skewingLocalForwardDirection = Consts.Beam.SD.SkewingLocalForwardDirectionDefault;
public Vector3 skewingLocalForwardDirectionNormalized
{
get
{
if (Mathf.Approximately(skewingLocalForwardDirection.z, 0.0f))
{
Debug.LogErrorFormat("Beam {0} has a skewingLocalForwardDirection with a null Z, which is forbidden", name);
return Vector3.forward;
}
else return skewingLocalForwardDirection.normalized;
}
}
public bool canHaveMeshSkewing { get { return geomMeshType == MeshType.Custom; } }
public bool hasMeshSkewing
{
get
{
if (!Config.Instance.featureEnabledMeshSkewing) return false;
if (!canHaveMeshSkewing) return false;
var dotForward = Vector3.Dot(skewingLocalForwardDirectionNormalized, Vector3.forward);
if (Mathf.Approximately(dotForward, 1.0f)) return false;
return true;
}
}
/// <summary>
/// Additional clipping plane transform. Will cut the beam according to the GameObject's position and rotation.
/// </summary>
public Transform clippingPlaneTransform = Consts.Beam.SD.ClippingPlaneTransformDefault;
public Vector4 additionalClippingPlane { get { return clippingPlaneTransform == null ? Vector4.zero : Utils.PlaneEquation(clippingPlaneTransform.forward, clippingPlaneTransform.position); } }
/// <summary>
/// Show the cone cap (only visible from inside)
/// </summary>
public bool geomCap = Consts.Beam.GeomCap;
/// <summary>
/// Light attenuation formula used to compute fading between 'fallOffStart' and 'fallOffEnd'
/// </summary>
public AttenuationEquation attenuationEquation = Consts.Beam.AttenuationEquationDefault;
/// <summary>
/// Custom blending mix between linear and quadratic attenuation formulas.
/// Only used if attenuationEquation is set to AttenuationEquation.Blend.
/// 0.0 = 100% Linear
/// 0.5 = Mix between 50% Linear and 50% Quadratic
/// 1.0 = 100% Quadratic
/// </summary>
[Range(Consts.Beam.AttenuationCustomBlendingMin, Consts.Beam.AttenuationCustomBlendingMax)]
public float attenuationCustomBlending = Consts.Beam.AttenuationCustomBlendingDefault;
/// <summary>
/// Proper lerp value between linear and quadratic attenuation, used by the shader.
/// </summary>
public float attenuationLerpLinearQuad {
get {
if (attenuationEquation == AttenuationEquation.Linear) return 0f;
else if (attenuationEquation == AttenuationEquation.Quadratic) return 1f;
return attenuationCustomBlending;
}
}
/// <summary>
/// Distance from the light source (in units) the beam will start to fade out.
/// </summary>
///
[FormerlySerializedAs("fadeStart")]
public float fallOffStart = Consts.Beam.FallOffStart;
[System.Obsolete("Use 'fallOffStart' instead")]
public float fadeStart { get { return fallOffStart; } set { fallOffStart = value; } }
/// <summary>
/// Distance from the light source (in units) the beam is entirely faded out.
/// </summary>
[FormerlySerializedAs("fadeEnd")]
public float fallOffEnd = Consts.Beam.FallOffEnd;
[System.Obsolete("Use 'fallOffEnd' instead")]
public float fadeEnd { get { return fallOffEnd; } set { fallOffEnd = value; } }
/// <summary>
/// Get the fallOffEnd value from the light (when attached to a Spotlight) or not
/// </summary>
[FormerlySerializedAs("fadeEndFromLight")]
public bool fallOffEndFromLight = true;
[System.Obsolete("Use 'fallOffEndFromLight' instead")]
public bool fadeEndFromLight { get { return fallOffEndFromLight; } set { fallOffEndFromLight = value; } }
public bool useFallOffEndFromAttachedLightSpot { get { return fallOffEndFromLight && lightSpotAttached != null; } }
/// <summary>
/// Distance multiplier to modulate the spotlight range.
/// </summary>
[Min(Consts.Beam.MultiplierMin)]
public float fallOffEndMultiplier = Consts.Beam.MultiplierDefault;
public float maxGeometryDistance { get { return fallOffEnd + Mathf.Max(Mathf.Abs(tiltFactor.x), Mathf.Abs(tiltFactor.y)); } }
/// <summary>
/// Distance from the world geometry the beam will fade.
/// 0 = hard intersection
/// Higher values produce soft intersection when the beam intersects other opaque geometry.
/// </summary>
public float depthBlendDistance = Consts.Beam.DepthBlendDistance;
/// <summary>
/// Distance from the camera the beam will fade.
/// 0 = hard intersection
/// Higher values produce soft intersection when the camera is near the cone triangles.
/// </summary>
public float cameraClippingDistance = Consts.Beam.CameraClippingDistance;
/// <summary>
/// Boost intensity factor when looking at the beam from the inside directly at the source.
/// </summary>
[Range(Consts.Beam.SD.GlareMin, Consts.Beam.SD.GlareMax)]
public float glareFrontal = Consts.Beam.SD.GlareFrontalDefault;
/// <summary>
/// Boost intensity factor when looking at the beam from behind.
/// </summary>
[Range(Consts.Beam.SD.GlareMin, Consts.Beam.SD.GlareMax)]
public float glareBehind = Consts.Beam.SD.GlareBehindDefault;
/// <summary>
/// Modulate the thickness of the beam when looking at it from the side.
/// Higher values produce thinner beam with softer transition at beam edges.
/// </summary>
[FormerlySerializedAs("fresnelPowOutside")]
public float fresnelPow = Consts.Beam.SD.FresnelPow;
/// <summary>
/// Enable 3D Noise effect and choose the mode
/// </summary>
public NoiseMode noiseMode = Consts.Beam.NoiseModeDefault;
public bool isNoiseEnabled { get { return noiseMode != NoiseMode.Disabled; } }
[System.Obsolete("Use 'noiseMode' instead")]
public bool noiseEnabled { get { return isNoiseEnabled; } set { noiseMode = value ? NoiseMode.WorldSpace : NoiseMode.Disabled; } }
/// <summary>
/// Contribution factor of the 3D Noise (when enabled).
/// Higher intensity means the noise contribution is stronger and more visible.
/// </summary>
[Range(Consts.Beam.NoiseIntensityMin, Consts.Beam.NoiseIntensityMax)]
public float noiseIntensity = Consts.Beam.NoiseIntensityDefault;
/// <summary>
/// Get the noiseScale value from the Global 3D Noise configuration
/// </summary>
public bool noiseScaleUseGlobal = true;
/// <summary>
/// 3D Noise texture scaling: higher scale make the noise more visible, but potentially less realistic.
/// </summary>
[Range(Consts.Beam.NoiseScaleMin, Consts.Beam.NoiseScaleMax)]
public float noiseScaleLocal = Consts.Beam.NoiseScaleDefault;
/// <summary>
/// Get the noiseVelocity value from the Global 3D Noise configuration
/// </summary>
public bool noiseVelocityUseGlobal = true;
/// <summary>
/// World Space direction and speed of the 3D Noise scrolling, simulating the fog/smoke movement.
/// </summary>
public Vector3 noiseVelocityLocal = Consts.Beam.NoiseVelocityDefault;
/// <summary>
/// Fade out starting distance.
/// Beyond this distance, the beam intensity will start to be dimmed.
/// </summary>
public float fadeOutBegin
{
get { return _FadeOutBegin; }
set { SetFadeOutValue(ref _FadeOutBegin, value); }
}
/// <summary>
/// Fade out ending distance.
/// Beyond this distance, the beam will be culled off to save on performance.
/// </summary>
public float fadeOutEnd
{
get { return _FadeOutEnd; }
set { SetFadeOutValue(ref _FadeOutEnd, value); }
}
/// <summary>
/// Is Fade Out feature enabled or not?
/// </summary>
public bool isFadeOutEnabled { get { return _FadeOutBegin >= 0 && _FadeOutEnd >= 0; } }
/// <summary>
/// - 3D: beam along the Z axis.
/// - 2D: beam along the X axis, so you won't have to rotate it to see it in 2D.
/// </summary>
public Dimensions dimensions = Consts.Beam.DimensionsDefault;
/// <summary>
/// Tilt the color and attenuation gradient compared to the global beam's direction.
/// Should be used with 'High' Shader Accuracy mode.
/// </summary>
public Vector2 tiltFactor = Consts.Beam.SD.TiltDefault;
public bool isTilted { get { return !tiltFactor.Approximately(Vector2.zero); } }
/// <summary>
/// Unique ID of the beam's sorting layer.
/// </summary>
public int sortingLayerID
{
get { return _SortingLayerID; }
set {
_SortingLayerID = value;
if (m_BeamGeom) m_BeamGeom.sortingLayerID = value;
}
}
/// <summary>
/// Name of the beam's sorting layer.
/// </summary>
public string sortingLayerName
{
get { return SortingLayer.IDToName(sortingLayerID); }
set { sortingLayerID = SortingLayer.NameToID(value); }
}
/// <summary>
/// The overlay priority within its layer.
/// Lower numbers are rendered first and subsequent numbers overlay those below.
/// </summary>
public int sortingOrder
{
get { return _SortingOrder; }
set
{
_SortingOrder = value;
if (m_BeamGeom) m_BeamGeom.sortingOrder = value;
}
}
/// <summary>
/// If true, the light beam will keep track of the changes of its own properties and the spotlight attached to it (if any) during playtime.
/// This would allow you to modify the light beam in realtime from Script, Animator and/or Timeline.
/// Enabling this feature is at very minor performance cost. So keep it disabled if you don't plan to modify this light beam during playtime.
/// </summary>
public bool trackChangesDuringPlaytime
{
get { return _TrackChangesDuringPlaytime; }
set { _TrackChangesDuringPlaytime = value; StartPlaytimeUpdateIfNeeded(); }
}
/// <summary> Is the beam currently tracking property changes? </summary>
public bool isCurrentlyTrackingChanges { get { return m_CoPlaytimeUpdate != null; } }
public override BeamGeometryAbstractBase GetBeamGeometry() { return m_BeamGeom; }
protected override void SetBeamGeometryNull() { m_BeamGeom = null; }
public int blendingModeAsInt { get { return Mathf.Clamp((int)blendingMode, 0, System.Enum.GetValues(typeof(BlendingMode)).Length); } }
public Quaternion beamInternalLocalRotation { get { return dimensions == Dimensions.Dim3D ? Quaternion.identity : Quaternion.LookRotation(Vector3.right, Vector3.up); } }
public Vector3 beamLocalForward { get { return dimensions == Dimensions.Dim3D ? Vector3.forward : Vector3.right; } }
public Vector3 beamGlobalForward { get { return transform.TransformDirection(beamLocalForward); } }
public override Vector3 GetLossyScale() { return dimensions == Dimensions.Dim3D ? transform.lossyScale : new Vector3(transform.lossyScale.z, transform.lossyScale.y, transform.lossyScale.x); }
public float raycastDistance {
get {
if (!hasMeshSkewing) return maxGeometryDistance;
else
{
var skewingZ = skewingLocalForwardDirectionNormalized.z;
return Mathf.Approximately(skewingZ, 0.0f) ? maxGeometryDistance : (maxGeometryDistance / skewingZ);
}
}
}
Vector3 ComputeRaycastGlobalVector(Vector3 localVec)
{
var rot = transform.rotation * beamInternalLocalRotation;
return rot * localVec;
}
public Vector3 raycastGlobalForward { get { return ComputeRaycastGlobalVector(hasMeshSkewing ? skewingLocalForwardDirectionNormalized : Vector3.forward); } }
public Vector3 raycastGlobalUp { get { return ComputeRaycastGlobalVector(Vector3.up); } }
public Vector3 raycastGlobalRight { get { return ComputeRaycastGlobalVector(Vector3.right); } }
// INTERNAL
public MaterialManager.SD.DynamicOcclusion _INTERNAL_DynamicOcclusionMode
{
get { return Config.Instance.featureEnabledDynamicOcclusion ? m_INTERNAL_DynamicOcclusionMode : MaterialManager.SD.DynamicOcclusion.Off; }
set { m_INTERNAL_DynamicOcclusionMode = value; }
}
public MaterialManager.SD.DynamicOcclusion _INTERNAL_DynamicOcclusionMode_Runtime { get { return m_INTERNAL_DynamicOcclusionMode_Runtime ? _INTERNAL_DynamicOcclusionMode : MaterialManager.SD.DynamicOcclusion.Off; } }
MaterialManager.SD.DynamicOcclusion m_INTERNAL_DynamicOcclusionMode = MaterialManager.SD.DynamicOcclusion.Off;
bool m_INTERNAL_DynamicOcclusionMode_Runtime = false;
public void _INTERNAL_SetDynamicOcclusionCallback(string shaderKeyword, MaterialModifier.Callback cb)
{
m_INTERNAL_DynamicOcclusionMode_Runtime = cb != null;
if (m_BeamGeom)
m_BeamGeom.SetDynamicOcclusionCallback(shaderKeyword, cb);
}
public delegate void OnWillCameraRenderCB(Camera cam);
public event OnWillCameraRenderCB onWillCameraRenderThisBeam;
public void _INTERNAL_OnWillCameraRenderThisBeam(Camera cam)
{
if (onWillCameraRenderThisBeam != null)
onWillCameraRenderThisBeam(cam);
}
public delegate void OnBeamGeometryInitialized();
private OnBeamGeometryInitialized m_OnBeamGeometryInitialized;
public void RegisterOnBeamGeometryInitializedCallback(OnBeamGeometryInitialized cb)
{
m_OnBeamGeometryInitialized += cb;
if(m_BeamGeom)
{
CallOnBeamGeometryInitializedCallback();
}
}
void CallOnBeamGeometryInitializedCallback()
{
if (m_OnBeamGeometryInitialized != null)
{
m_OnBeamGeometryInitialized();
m_OnBeamGeometryInitialized = null;
}
}
[FormerlySerializedAs("trackChangesDuringPlaytime")]
[SerializeField] bool _TrackChangesDuringPlaytime = false;
[SerializeField] int _SortingLayerID = 0;
[SerializeField] int _SortingOrder = 0;
[FormerlySerializedAs("fadeOutBegin")]
[SerializeField] float _FadeOutBegin = Consts.Beam.FadeOutBeginDefault;
[FormerlySerializedAs("fadeOutEnd")]
[SerializeField] float _FadeOutEnd = Consts.Beam.FadeOutEndDefault;
void SetFadeOutValue(ref float propToChange, float value)
{
bool wasEnabled = isFadeOutEnabled;
propToChange = value;
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
if (isFadeOutEnabled != wasEnabled)
OnFadeOutStateChanged();
}
}
void OnFadeOutStateChanged()
{
#if UNITY_EDITOR
if (Application.isPlaying)
#endif
{
// Restart only when fadeout is enabled: on disable, the coroutine will kill itself automatically
if (isFadeOutEnabled && m_BeamGeom) m_BeamGeom.RestartFadeOutCoroutine();
}
}
/// Internal property used for QA testing purpose, do not change
public uint _INTERNAL_InstancedMaterialGroupID { get; protected set; }
BeamGeometrySD m_BeamGeom = null;
Coroutine m_CoPlaytimeUpdate = null;
#if UNITY_EDITOR
public override int _EDITOR_GetInstancedMaterialID() { return m_BeamGeom ? m_BeamGeom._EDITOR_InstancedMaterialID : int.MinValue; }
static VolumetricLightBeamSD[] _EditorFindAllInstances()
{
return Resources.FindObjectsOfTypeAll<VolumetricLightBeamSD>();
}
public static void _EditorSetAllMeshesDirty()
{
foreach (var instance in _EditorFindAllInstances())
instance._EditorSetMeshDirty();
}
public static void _EditorSetAllBeamGeomDirty()
{
foreach (var instance in _EditorFindAllInstances())
instance.m_EditorDirtyFlags |= EditorDirtyFlags.FullBeamGeomGAO;
}
#endif // UNITY_EDITOR
public string meshStats
{
get
{
Mesh mesh = m_BeamGeom ? m_BeamGeom.coneMesh : null;
if (mesh) return string.Format("Cone angle: {0:0.0} degrees\nMesh: {1} vertices, {2} triangles", coneAngle, mesh.vertexCount, mesh.triangles.Length / 3);
else return "no mesh available";
}
}
public int meshVerticesCount { get { return (m_BeamGeom && m_BeamGeom.coneMesh) ? m_BeamGeom.coneMesh.vertexCount : 0; } }
public int meshTrianglesCount { get { return (m_BeamGeom && m_BeamGeom.coneMesh) ? m_BeamGeom.coneMesh.triangles.Length / 3 : 0; } }
/// <summary>
/// Returns a value indicating if the world position passed in argument is inside the light beam or not.
/// This functions treats the beam like infinite (like the beam had an infinite length and never fell off)
/// </summary>
/// <param name="posWS">World position</param>
/// <returns>
/// < 0 position is out
/// = 0 position is exactly on the beam geometry
/// > 0 position is inside the cone
/// </returns>
public float GetInsideBeamFactor(Vector3 posWS) { return GetInsideBeamFactorFromObjectSpacePos(transform.InverseTransformPoint(posWS)); }
public float GetInsideBeamFactorFromObjectSpacePos(Vector3 posOS)
{
if(dimensions == Dimensions.Dim2D)
{
posOS = new Vector3(posOS.z, posOS.y, posOS.x);
}
if (posOS.z < 0f) return -1f;
Vector2 posOSXY = posOS.xy();
if (hasMeshSkewing)
{
Vector3 localForwardDirN = skewingLocalForwardDirectionNormalized;
posOSXY -= localForwardDirN.xy() * (posOS.z / localForwardDirN.z);
}
// Compute a factor to know how far inside the beam cone the camera is
var triangle2D = new Vector2(posOSXY.magnitude, posOS.z + coneApexOffsetZ).normalized;
const float maxRadiansDiff = 0.1f;
float slopeRad = (coneAngle * Mathf.Deg2Rad) / 2;
return Mathf.Clamp((Mathf.Abs(Mathf.Sin(slopeRad)) - Mathf.Abs(triangle2D.x)) / maxRadiansDiff, -1, 1);
}
[System.Obsolete("Use 'GenerateGeometry()' instead")]
public void Generate() { GenerateGeometry(); }
/// <summary>
/// Regenerate the beam mesh (and also the material).
/// This can be slow (it recreates a mesh from scratch), so don't call this function during playtime.
/// You would need to call this function only if you want to change the properties 'geomSides' and 'geomCap' during playtime.
/// Otherwise, for the other properties, just enable 'trackChangesDuringPlaytime', or manually call 'UpdateAfterManualPropertyChange()'
/// </summary>
public virtual void GenerateGeometry()
{
HandleBackwardCompatibility(pluginVersion, Version.Current);
pluginVersion = Version.Current;
ValidateProperties();
if (m_BeamGeom == null)
{
m_BeamGeom = Utils.NewWithComponent<BeamGeometrySD>("Beam Geometry");
m_BeamGeom.Initialize(this);
CallOnBeamGeometryInitializedCallback();
}
m_BeamGeom.RegenerateMesh(enabled);
}
/// <summary>
/// Update the beam material and its bounds.
/// Calling manually this function is useless if your beam has its property 'trackChangesDuringPlaytime' enabled
/// (because then this function is automatically called each frame).
/// However, if 'trackChangesDuringPlaytime' is disabled, and you change a property via Script for example,
/// you need to call this function to take the property change into account.
/// All properties changes are took into account, expect 'geomSides' and 'geomCap' which require to regenerate the geometry via 'GenerateGeometry()'
/// </summary>
public virtual void UpdateAfterManualPropertyChange()
{
ValidateProperties();
if (m_BeamGeom) m_BeamGeom.UpdateMaterialAndBounds();
}
#if !UNITY_EDITOR
void Start()
{
InitLightSpotAttachedCached();
// In standalone builds, simply generate the geometry once in Start
GenerateGeometry();
}
#else
void Start()
{
if (Application.isPlaying)
{
InitLightSpotAttachedCached();
GenerateGeometry();
m_EditorDirtyFlags = EditorDirtyFlags.Clean;
}
else
{
// In Editor, creating geometry from Start and/or OnValidate generates warning in Unity 2017.
// So we do it from Update
m_EditorDirtyFlags = EditorDirtyFlags.Everything;
}
StartPlaytimeUpdateIfNeeded();
}
void OnValidate()
{
m_EditorDirtyFlags |= EditorDirtyFlags.Props; // Props have been modified from Editor
}
void Update() // EDITOR ONLY
{
EditorHandleLightPropertiesUpdate(); // Handle edition of light properties in Editor
if (m_EditorDirtyFlags == EditorDirtyFlags.Clean)
{
if (Application.isPlaying)
{
if (!trackChangesDuringPlaytime) // during Playtime, realtime changes are handled by CoUpdateDuringPlaytime
return;
}
}
else
{
if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.Mesh))
{
if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.BeamGeomGAO))
DestroyBeam();
GenerateGeometry(); // regenerate everything
}
else if (m_EditorDirtyFlags.HasFlag(EditorDirtyFlags.Props))
{
ValidateProperties();
}
}
// If we modify the attached Spotlight properties, or if we animate the beam via Unity 2017's timeline,
// we are not notified of properties changes. So we update the material anyway.
UpdateAfterManualPropertyChange();
m_EditorDirtyFlags = EditorDirtyFlags.Clean;
}
public void Reset()
{
colorMode = Consts.Beam.ColorModeDefault;
color = Consts.Beam.FlatColor;
colorFromLight = true;
intensityFromLight = true;
intensityModeAdvanced = false;
intensityInside = Consts.Beam.IntensityDefault;
intensityOutside = Consts.Beam.IntensityDefault;
intensityMultiplier = Consts.Beam.MultiplierDefault;
hdrpExposureWeight = Consts.Beam.HDRPExposureWeightDefault;
blendingMode = Consts.Beam.BlendingModeDefault;
shaderAccuracy = Consts.Beam.ShaderAccuracyDefault;
spotAngleFromLight = true;
spotAngle = Consts.Beam.SpotAngleDefault;
spotAngleMultiplier = Consts.Beam.MultiplierDefault;
coneRadiusStart = Consts.Beam.ConeRadiusStart;
geomMeshType = Consts.Beam.GeomMeshType;
geomCustomSides = Consts.Beam.GeomSidesDefault;
geomCustomSegments = Consts.Beam.GeomSegmentsDefault;
geomCap = Consts.Beam.GeomCap;
attenuationEquation = Consts.Beam.AttenuationEquationDefault;
attenuationCustomBlending = Consts.Beam.AttenuationCustomBlendingDefault;
fallOffEndFromLight = true;
fallOffStart = Consts.Beam.FallOffStart;
fallOffEnd = Consts.Beam.FallOffEnd;
fallOffEndMultiplier = Consts.Beam.MultiplierDefault;
depthBlendDistance = Consts.Beam.DepthBlendDistance;
cameraClippingDistance = Consts.Beam.CameraClippingDistance;
glareFrontal = Consts.Beam.SD.GlareFrontalDefault;
glareBehind = Consts.Beam.SD.GlareBehindDefault;
fresnelPow = Consts.Beam.SD.FresnelPow;
noiseMode = Consts.Beam.NoiseModeDefault;
noiseIntensity = Consts.Beam.NoiseIntensityDefault;
noiseScaleUseGlobal = true;
noiseScaleLocal = Consts.Beam.NoiseScaleDefault;
noiseVelocityUseGlobal = true;
noiseVelocityLocal = Consts.Beam.NoiseVelocityDefault;
sortingLayerID = 0;
sortingOrder = 0;
fadeOutBegin = Consts.Beam.FadeOutBeginDefault;
fadeOutEnd = Consts.Beam.FadeOutEndDefault;
dimensions = Consts.Beam.DimensionsDefault;
tiltFactor = Consts.Beam.SD.TiltDefault;
skewingLocalForwardDirection = Consts.Beam.SD.SkewingLocalForwardDirectionDefault;
clippingPlaneTransform = Consts.Beam.SD.ClippingPlaneTransformDefault;
trackChangesDuringPlaytime = false;
m_EditorDirtyFlags = EditorDirtyFlags.Everything;
}
#endif
void OnEnable()
{
if (m_BeamGeom)
m_BeamGeom.OnMasterEnable();
StartPlaytimeUpdateIfNeeded();
#if UNITY_EDITOR
EditorLoadPrefs();
#endif
}
void OnDisable()
{
if (m_BeamGeom)
m_BeamGeom.OnMasterDisable();
m_CoPlaytimeUpdate = null;
}
void StartPlaytimeUpdateIfNeeded()
{
if (Application.isPlaying && trackChangesDuringPlaytime && m_CoPlaytimeUpdate == null)
{
m_CoPlaytimeUpdate = StartCoroutine(CoPlaytimeUpdate());
}
}
IEnumerator CoPlaytimeUpdate()
{
while (trackChangesDuringPlaytime && enabled)
{
UpdateAfterManualPropertyChange();
yield return null;
}
m_CoPlaytimeUpdate = null;
}
void AssignPropertiesFromAttachedSpotLight()
{
var lightSpot = lightSpotAttached;
if (lightSpot)
{
Debug.AssertFormat(lightSpot.type == LightType.Spot, "Light attached to {0} '{1}' must be a Spot", ClassName, name);
if (intensityFromLight)
{
intensityModeAdvanced = false;
intensityGlobal = SpotLightHelper.GetIntensity(lightSpot) * intensityMultiplier;
}
if (fallOffEndFromLight) fallOffEnd = SpotLightHelper.GetFallOffEnd(lightSpot) * fallOffEndMultiplier;
if (spotAngleFromLight) spotAngle = Mathf.Clamp(SpotLightHelper.GetSpotAngle(lightSpot) * spotAngleMultiplier, Consts.Beam.SpotAngleMin, Consts.Beam.SpotAngleMax);
if (colorFromLight)
{
colorMode = ColorMode.Flat;
#if VLB_LIGHT_TEMPERATURE_SUPPORT
if (useColorTemperatureFromAttachedLightSpot)
{
Color colorFromTemp = Mathf.CorrelatedColorTemperatureToRGB(lightSpot.colorTemperature);
var finalColor = lightSpot.color.linear * colorFromTemp;
color = finalColor.gamma;
}
else
#endif
{
color = lightSpot.color;
}
}
}
}
void ClampProperties()
{
intensityInside = Mathf.Max(intensityInside, Consts.Beam.IntensityMin);
intensityOutside = Mathf.Max(intensityOutside, Consts.Beam.IntensityMin);
intensityMultiplier = Mathf.Max(intensityMultiplier, Consts.Beam.MultiplierMin);
attenuationCustomBlending = Mathf.Clamp(attenuationCustomBlending, Consts.Beam.AttenuationCustomBlendingMin, Consts.Beam.AttenuationCustomBlendingMax);
fallOffEnd = Mathf.Max(Consts.Beam.FallOffDistancesMinThreshold, fallOffEnd);
fallOffStart = Mathf.Clamp(fallOffStart, 0f, fallOffEnd - Consts.Beam.FallOffDistancesMinThreshold);
fallOffEndMultiplier = Mathf.Max(fallOffEndMultiplier, Consts.Beam.MultiplierMin);
spotAngle = Mathf.Clamp(spotAngle, Consts.Beam.SpotAngleMin, Consts.Beam.SpotAngleMax);
spotAngleMultiplier = Mathf.Max(spotAngleMultiplier, Consts.Beam.MultiplierMin);
coneRadiusStart = Mathf.Max(coneRadiusStart, 0f);
depthBlendDistance = Mathf.Max(depthBlendDistance, 0f);
cameraClippingDistance = Mathf.Max(cameraClippingDistance, 0f);
geomCustomSides = Mathf.Clamp(geomCustomSides, Consts.Beam.GeomSidesMin, Consts.Beam.GeomSidesMax);
geomCustomSegments = Mathf.Clamp(geomCustomSegments, Consts.Beam.GeomSegmentsMin, Consts.Beam.GeomSegmentsMax);
fresnelPow = Mathf.Max(0f, fresnelPow);
glareBehind = Mathf.Clamp(glareBehind, Consts.Beam.SD.GlareMin, Consts.Beam.SD.GlareMax);
glareFrontal = Mathf.Clamp(glareFrontal, Consts.Beam.SD.GlareMin, Consts.Beam.SD.GlareMax);
noiseIntensity = Mathf.Clamp(noiseIntensity, Consts.Beam.NoiseIntensityMin, Consts.Beam.NoiseIntensityMax);
}
void ValidateProperties()
{
AssignPropertiesFromAttachedSpotLight();
ClampProperties();
}
void HandleBackwardCompatibility(int serializedVersion, int newVersion)
{
if (serializedVersion == -1) return; // freshly new spawned entity: nothing to do
if (serializedVersion == newVersion) return; // same version: nothing to do
if (serializedVersion < 1301)
{
// quadratic attenuation is a new feature of 1.3
attenuationEquation = AttenuationEquation.Linear;
}
if (serializedVersion < 1501)
{
// custom mesh is a new feature of 1.5
geomMeshType = MeshType.Custom;
geomCustomSegments = 5;
}
if (serializedVersion < 1610)
{
// intensity global/advanced mode is a feature of 1.61
intensityFromLight = false;
intensityModeAdvanced = !Mathf.Approximately(intensityInside, intensityOutside);
}
if (serializedVersion < 1910)
{
// prevent from triggering error message if inside and outside intensity are not equal with advanced mode disabled
if(!intensityModeAdvanced && !Mathf.Approximately(intensityInside, intensityOutside))
{
intensityInside = intensityOutside;
}
}
Utils.MarkCurrentSceneDirty();
}
#if UNITY_EDITOR
public static bool editorShowTiltFactor = false;
public static bool editorShowClippingPlane = false;
private static bool editorPrefsLoaded = false;
static void EditorLoadPrefs()
{
if (!editorPrefsLoaded)
{
editorShowTiltFactor = UnityEditor.EditorPrefs.GetBool(EditorPrefsStrings.Beam.PrefShowTiltDir, false);
editorPrefsLoaded = true;
}
}
void OnDrawGizmos()
{
#if DEBUG_SHOW_APEX
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.color = Color.green;
Gizmos.DrawWireSphere(coneApexPositionLocal, 0.025f);
#endif
#if DEBUG_GLOBAL_VECTORS
Debug.DrawLine(transform.position, transform.position + beamGlobalForward, Color.blue);
#endif
#if DEBUG_GLOBAL_RAYCAST_VECTORS
Debug.DrawLine(transform.position, transform.position + raycastGlobalForward, Color.blue);
Debug.DrawLine(transform.position, transform.position + raycastGlobalRight, Color.red);
Debug.DrawLine(transform.position, transform.position + raycastGlobalUp, Color.green);
#endif
if (editorShowTiltFactor)
{
Utils.GizmosDrawPlane(
new Vector3(tiltFactor.x, tiltFactor.y, 1.0f),
Vector3.zero,
Color.white,
transform.localToWorldMatrix,
0.25f,
0.5f);
}
if (editorShowClippingPlane && clippingPlaneTransform != null)
{
float kPlaneSize = 0.7f;
Utils.GizmosDrawPlane(
Vector3.forward,
Vector3.zero,
Color.white,
clippingPlaneTransform.localToWorldMatrix,
kPlaneSize,
kPlaneSize * 0.5f);
}
}
#endif // UNITY_EDITOR
}
}