VolumetricLineBehavior.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. using UnityEngine;
  2. namespace VolumetricLines
  3. {
  4. /// <summary>
  5. /// Render a single volumetric line
  6. ///
  7. /// Based on the Volumetric lines algorithm by Sebastien Hillaire
  8. /// http://sebastien.hillaire.free.fr/index.php?option=com_content&view=article&id=57&Itemid=74
  9. ///
  10. /// Thread in the Unity3D Forum:
  11. /// http://forum.unity3d.com/threads/181618-Volumetric-lines
  12. ///
  13. /// Unity3D port by Johannes Unterguggenberger
  14. /// johannes.unterguggenberger@gmail.com
  15. ///
  16. /// Thanks to Michael Probst for support during development.
  17. ///
  18. /// Thanks for bugfixes and improvements to Unity Forum User "Mistale"
  19. /// http://forum.unity3d.com/members/102350-Mistale
  20. ///
  21. /// Shader code optimization and cleanup by Lex Darlog (aka DRL)
  22. /// http://forum.unity3d.com/members/lex-drl.67487/
  23. ///
  24. /// </summary>
  25. [RequireComponent(typeof(MeshFilter))]
  26. [RequireComponent(typeof(MeshRenderer))]
  27. [ExecuteInEditMode]
  28. public class VolumetricLineBehavior : MonoBehaviour
  29. {
  30. // Used to compute the average value of all the Vector3's components:
  31. static readonly Vector3 Average = new Vector3(1f/3f, 1f/3f, 1f/3f);
  32. #region private variables
  33. /// <summary>
  34. /// Template material to be used
  35. /// </summary>
  36. [SerializeField]
  37. public Material m_templateMaterial;
  38. /// <summary>
  39. /// Set to false in order to change the material's properties as specified in this script.
  40. /// Set to true in order to *initially* leave the material's properties as they are in the template material.
  41. /// </summary>
  42. [SerializeField]
  43. private bool m_doNotOverwriteTemplateMaterialProperties;
  44. /// <summary>
  45. /// The start position relative to the GameObject's origin
  46. /// </summary>
  47. [SerializeField]
  48. private Vector3 m_startPos;
  49. /// <summary>
  50. /// The end position relative to the GameObject's origin
  51. /// </summary>
  52. [SerializeField]
  53. private Vector3 m_endPos = new Vector3(0f, 0f, 100f);
  54. /// <summary>
  55. /// Line Color
  56. /// </summary>
  57. [SerializeField]
  58. private Color m_lineColor;
  59. /// <summary>
  60. /// The width of the line
  61. /// </summary>
  62. [SerializeField]
  63. private float m_lineWidth;
  64. /// <summary>
  65. /// Light saber factor
  66. /// </summary>
  67. [SerializeField]
  68. [Range(0.0f, 1.0f)]
  69. private float m_lightSaberFactor;
  70. /// <summary>
  71. /// This GameObject's specific material
  72. /// </summary>
  73. private Material m_material;
  74. /// <summary>
  75. /// This GameObject's mesh filter
  76. /// </summary>
  77. private MeshFilter m_meshFilter;
  78. #endregion
  79. #region properties
  80. /// <summary>
  81. /// Gets or sets the tmplate material.
  82. /// Setting this will only have an impact once.
  83. /// Subsequent changes will be ignored.
  84. /// </summary>
  85. public Material TemplateMaterial
  86. {
  87. get { return m_templateMaterial; }
  88. set { m_templateMaterial = value; }
  89. }
  90. /// <summary>
  91. /// Gets or sets whether or not the template material properties
  92. /// should be used (false) or if the properties of this MonoBehavior
  93. /// instance should be used (true, default).
  94. /// Setting this will only have an impact once, and then only if it
  95. /// is set before TemplateMaterial has been assigned.
  96. /// </summary>
  97. public bool DoNotOverwriteTemplateMaterialProperties
  98. {
  99. get { return m_doNotOverwriteTemplateMaterialProperties; }
  100. set { m_doNotOverwriteTemplateMaterialProperties = value; }
  101. }
  102. /// <summary>
  103. /// Get or set the line color of this volumetric line's material
  104. /// </summary>
  105. public Color LineColor
  106. {
  107. get { return m_lineColor; }
  108. set
  109. {
  110. CreateMaterial();
  111. if (null != m_material)
  112. {
  113. m_lineColor = value;
  114. m_material.color = m_lineColor;
  115. }
  116. }
  117. }
  118. /// <summary>
  119. /// Get or set the line width of this volumetric line's material
  120. /// </summary>
  121. public float LineWidth
  122. {
  123. get { return m_lineWidth; }
  124. set
  125. {
  126. CreateMaterial();
  127. if (null != m_material)
  128. {
  129. m_lineWidth = value;
  130. m_material.SetFloat("_LineWidth", m_lineWidth);
  131. }
  132. UpdateBounds();
  133. }
  134. }
  135. /// <summary>
  136. /// Get or set the light saber factor of this volumetric line's material
  137. /// </summary>
  138. public float LightSaberFactor
  139. {
  140. get { return m_lightSaberFactor; }
  141. set
  142. {
  143. CreateMaterial();
  144. if (null != m_material)
  145. {
  146. m_lightSaberFactor = value;
  147. m_material.SetFloat("_LightSaberFactor", m_lightSaberFactor);
  148. }
  149. }
  150. }
  151. /// <summary>
  152. /// Get or set the start position of this volumetric line's mesh
  153. /// </summary>
  154. public Vector3 StartPos
  155. {
  156. get { return m_startPos; }
  157. set
  158. {
  159. m_startPos = value;
  160. SetStartAndEndPoints(m_startPos, m_endPos);
  161. }
  162. }
  163. /// <summary>
  164. /// Get or set the end position of this volumetric line's mesh
  165. /// </summary>
  166. public Vector3 EndPos
  167. {
  168. get { return m_endPos; }
  169. set
  170. {
  171. m_endPos = value;
  172. SetStartAndEndPoints(m_startPos, m_endPos);
  173. }
  174. }
  175. #endregion
  176. #region methods
  177. /// <summary>
  178. /// Creates a copy of the template material for this instance
  179. /// </summary>
  180. private void CreateMaterial()
  181. {
  182. if (null == m_material || null == GetComponent<MeshRenderer>().sharedMaterial)
  183. {
  184. if (null != m_templateMaterial)
  185. {
  186. m_material = Material.Instantiate(m_templateMaterial);
  187. GetComponent<MeshRenderer>().sharedMaterial = m_material;
  188. SetAllMaterialProperties();
  189. }
  190. else
  191. {
  192. m_material = GetComponent<MeshRenderer>().sharedMaterial;
  193. }
  194. }
  195. }
  196. /// <summary>
  197. /// Destroys the copy of the template material which was used for this instance
  198. /// </summary>
  199. private void DestroyMaterial()
  200. {
  201. if (null != m_material)
  202. {
  203. DestroyImmediate(m_material);
  204. m_material = null;
  205. }
  206. }
  207. /// <summary>
  208. /// Calculates the (approximated) _LineScale factor based on the object's scale.
  209. /// </summary>
  210. private float CalculateLineScale()
  211. {
  212. return Vector3.Dot(transform.lossyScale, Average);
  213. }
  214. /// <summary>
  215. /// Updates the line scaling of this volumetric line based on the current object scaling.
  216. /// </summary>
  217. public void UpdateLineScale()
  218. {
  219. if (null != m_material)
  220. {
  221. m_material.SetFloat("_LineScale", CalculateLineScale());
  222. }
  223. }
  224. /// <summary>
  225. /// Sets all material properties (color, width, light saber factor, start-, endpos)
  226. /// </summary>
  227. private void SetAllMaterialProperties()
  228. {
  229. SetStartAndEndPoints(m_startPos, m_endPos);
  230. if (null != m_material)
  231. {
  232. if (!m_doNotOverwriteTemplateMaterialProperties)
  233. {
  234. m_material.color = m_lineColor;
  235. m_material.SetFloat("_LineWidth", m_lineWidth);
  236. m_material.SetFloat("_LightSaberFactor", m_lightSaberFactor);
  237. }
  238. UpdateLineScale();
  239. }
  240. }
  241. /// <summary>
  242. /// Calculate the bounds of this line based on start and end points,
  243. /// the line width, and the scaling of the object.
  244. /// </summary>
  245. private Bounds CalculateBounds()
  246. {
  247. var maxWidth = Mathf.Max(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z);
  248. var scaledLineWidth = maxWidth * LineWidth * 0.5f;
  249. var min = new Vector3(
  250. Mathf.Min(m_startPos.x, m_endPos.x) - scaledLineWidth,
  251. Mathf.Min(m_startPos.y, m_endPos.y) - scaledLineWidth,
  252. Mathf.Min(m_startPos.z, m_endPos.z) - scaledLineWidth
  253. );
  254. var max = new Vector3(
  255. Mathf.Max(m_startPos.x, m_endPos.x) + scaledLineWidth,
  256. Mathf.Max(m_startPos.y, m_endPos.y) + scaledLineWidth,
  257. Mathf.Max(m_startPos.z, m_endPos.z) + scaledLineWidth
  258. );
  259. return new Bounds
  260. {
  261. min = min,
  262. max = max
  263. };
  264. }
  265. /// <summary>
  266. /// Updates the bounds of this line according to the current properties,
  267. /// which there are: start point, end point, line width, scaling of the object.
  268. /// </summary>
  269. public void UpdateBounds()
  270. {
  271. if (null != m_meshFilter)
  272. {
  273. var mesh = m_meshFilter.sharedMesh;
  274. Debug.Assert(null != mesh);
  275. if (null != mesh)
  276. {
  277. mesh.bounds = CalculateBounds();
  278. }
  279. }
  280. }
  281. /// <summary>
  282. /// Sets the start and end points - updates the data of the Mesh.
  283. /// </summary>
  284. public void SetStartAndEndPoints(Vector3 startPoint, Vector3 endPoint)
  285. {
  286. m_startPos = startPoint;
  287. m_endPos = endPoint;
  288. Vector3[] vertexPositions = {
  289. m_startPos,
  290. m_startPos,
  291. m_startPos,
  292. m_startPos,
  293. m_endPos,
  294. m_endPos,
  295. m_endPos,
  296. m_endPos,
  297. };
  298. Vector3[] other = {
  299. m_endPos,
  300. m_endPos,
  301. m_endPos,
  302. m_endPos,
  303. m_startPos,
  304. m_startPos,
  305. m_startPos,
  306. m_startPos,
  307. };
  308. if (null != m_meshFilter)
  309. {
  310. var mesh = m_meshFilter.sharedMesh;
  311. Debug.Assert(null != mesh);
  312. if (null != mesh)
  313. {
  314. mesh.vertices = vertexPositions;
  315. mesh.normals = other;
  316. UpdateBounds();
  317. }
  318. }
  319. }
  320. #endregion
  321. #region event functions
  322. void Start ()
  323. {
  324. Mesh mesh = new Mesh();
  325. m_meshFilter = GetComponent<MeshFilter>();
  326. m_meshFilter.mesh = mesh;
  327. SetStartAndEndPoints(m_startPos, m_endPos);
  328. mesh.uv = VolumetricLineVertexData.TexCoords;
  329. mesh.uv2 = VolumetricLineVertexData.VertexOffsets;
  330. mesh.SetIndices(VolumetricLineVertexData.Indices, MeshTopology.Triangles, 0);
  331. CreateMaterial();
  332. // TODO: Need to set vertices before assigning new Mesh to the MeshFilter's mesh property => Why?
  333. }
  334. void OnDestroy()
  335. {
  336. if (null != m_meshFilter)
  337. {
  338. if (Application.isPlaying)
  339. {
  340. Mesh.Destroy(m_meshFilter.sharedMesh);
  341. }
  342. else // avoid "may not be called from edit mode" error
  343. {
  344. Mesh.DestroyImmediate(m_meshFilter.sharedMesh);
  345. }
  346. m_meshFilter.sharedMesh = null;
  347. }
  348. DestroyMaterial();
  349. }
  350. void Update()
  351. {
  352. if (transform.hasChanged)
  353. {
  354. UpdateLineScale();
  355. UpdateBounds();
  356. }
  357. }
  358. void OnValidate()
  359. {
  360. // This function is called when the script is loaded or a value is changed in the inspector (Called in the editor only).
  361. // => make sure, everything stays up-to-date
  362. if(string.IsNullOrEmpty(gameObject.scene.name) || string.IsNullOrEmpty(gameObject.scene.path)) {
  363. return; // ...but not if a Prefab is selected! (Only if we're using it within a scene.)
  364. }
  365. CreateMaterial();
  366. SetAllMaterialProperties();
  367. UpdateBounds();
  368. }
  369. void OnDrawGizmos()
  370. {
  371. Gizmos.color = Color.green;
  372. Gizmos.DrawLine(gameObject.transform.TransformPoint(m_startPos), gameObject.transform.TransformPoint(m_endPos));
  373. }
  374. #endregion
  375. }
  376. }