Last active
August 16, 2024 22:57
-
-
Save mathiassoeholm/15f3eeda606e9be543165360615c8bef to your computer and use it in GitHub Desktop.
Similar to Unity's LineRenderer, but renders a cylindrical mesh.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Author: Mathias Soeholm | |
// Date: 05/10/2016 | |
// No license, do whatever you want with this script | |
using UnityEngine; | |
using UnityEngine.Serialization; | |
[ExecuteInEditMode] | |
public class TubeRenderer : MonoBehaviour | |
{ | |
[SerializeField] Vector3[] _positions; | |
[SerializeField] int _sides; | |
[SerializeField] float _radiusOne; | |
[SerializeField] float _radiusTwo; | |
[SerializeField] bool _useWorldSpace = true; | |
[SerializeField] bool _useTwoRadii = false; | |
private Vector3[] _vertices; | |
private Mesh _mesh; | |
private MeshFilter _meshFilter; | |
private MeshRenderer _meshRenderer; | |
public Material material | |
{ | |
get { return _meshRenderer.material; } | |
set { _meshRenderer.material = value; } | |
} | |
void Awake() | |
{ | |
_meshFilter = GetComponent<MeshFilter>(); | |
if (_meshFilter == null) | |
{ | |
_meshFilter = gameObject.AddComponent<MeshFilter>(); | |
} | |
_meshRenderer = GetComponent<MeshRenderer>(); | |
if (_meshRenderer == null) | |
{ | |
_meshRenderer = gameObject.AddComponent<MeshRenderer>(); | |
} | |
_mesh = new Mesh(); | |
_meshFilter.mesh = _mesh; | |
} | |
private void OnEnable() | |
{ | |
_meshRenderer.enabled = true; | |
} | |
private void OnDisable() | |
{ | |
_meshRenderer.enabled = false; | |
} | |
void Update () | |
{ | |
GenerateMesh(); | |
} | |
private void OnValidate() | |
{ | |
_sides = Mathf.Max(3, _sides); | |
} | |
public void SetPositions(Vector3[] positions) | |
{ | |
_positions = positions; | |
GenerateMesh(); | |
} | |
private void GenerateMesh() | |
{ | |
if (_mesh == null || _positions == null || _positions.Length <= 1) | |
{ | |
_mesh = new Mesh(); | |
return; | |
} | |
var verticesLength = _sides*_positions.Length; | |
if (_vertices == null || _vertices.Length != verticesLength) | |
{ | |
_vertices = new Vector3[verticesLength]; | |
var indices = GenerateIndices(); | |
var uvs = GenerateUVs(); | |
if (verticesLength > _mesh.vertexCount) | |
{ | |
_mesh.vertices = _vertices; | |
_mesh.triangles = indices; | |
_mesh.uv = uvs; | |
} | |
else | |
{ | |
_mesh.triangles = indices; | |
_mesh.vertices = _vertices; | |
_mesh.uv = uvs; | |
} | |
} | |
var currentVertIndex = 0; | |
for (int i = 0; i < _positions.Length; i++) | |
{ | |
var circle = CalculateCircle(i); | |
foreach (var vertex in circle) | |
{ | |
_vertices[currentVertIndex++] = _useWorldSpace ? transform.InverseTransformPoint(vertex) : vertex; | |
} | |
} | |
_mesh.vertices = _vertices; | |
_mesh.RecalculateNormals(); | |
_mesh.RecalculateBounds(); | |
_meshFilter.mesh = _mesh; | |
} | |
private Vector2[] GenerateUVs() | |
{ | |
var uvs = new Vector2[_positions.Length*_sides]; | |
for (int segment = 0; segment < _positions.Length; segment++) | |
{ | |
for (int side = 0; side < _sides; side++) | |
{ | |
var vertIndex = (segment * _sides + side); | |
var u = side/(_sides-1f); | |
var v = segment/(_positions.Length-1f); | |
uvs[vertIndex] = new Vector2(u, v); | |
} | |
} | |
return uvs; | |
} | |
private int[] GenerateIndices() | |
{ | |
// Two triangles and 3 vertices | |
var indices = new int[_positions.Length*_sides*2*3]; | |
var currentIndicesIndex = 0; | |
for (int segment = 1; segment < _positions.Length; segment++) | |
{ | |
for (int side = 0; side < _sides; side++) | |
{ | |
var vertIndex = (segment*_sides + side); | |
var prevVertIndex = vertIndex - _sides; | |
// Triangle one | |
indices[currentIndicesIndex++] = prevVertIndex; | |
indices[currentIndicesIndex++] = (side == _sides - 1) ? (vertIndex - (_sides - 1)) : (vertIndex + 1); | |
indices[currentIndicesIndex++] = vertIndex; | |
// Triangle two | |
indices[currentIndicesIndex++] = (side == _sides - 1) ? (prevVertIndex - (_sides - 1)) : (prevVertIndex + 1); | |
indices[currentIndicesIndex++] = (side == _sides - 1) ? (vertIndex - (_sides - 1)) : (vertIndex + 1); | |
indices[currentIndicesIndex++] = prevVertIndex; | |
} | |
} | |
return indices; | |
} | |
private Vector3[] CalculateCircle(int index) | |
{ | |
var dirCount = 0; | |
var forward = Vector3.zero; | |
// If not first index | |
if (index > 0) | |
{ | |
forward += (_positions[index] - _positions[index - 1]).normalized; | |
dirCount++; | |
} | |
// If not last index | |
if (index < _positions.Length-1) | |
{ | |
forward += (_positions[index + 1] - _positions[index]).normalized; | |
dirCount++; | |
} | |
// Forward is the average of the connecting edges directions | |
forward = (forward/dirCount).normalized; | |
var side = Vector3.Cross(forward, forward+new Vector3(.123564f, .34675f, .756892f)).normalized; | |
var up = Vector3.Cross(forward, side).normalized; | |
var circle = new Vector3[_sides]; | |
var angle = 0f; | |
var angleStep = (2*Mathf.PI)/_sides; | |
var t = index / (_positions.Length-1f); | |
var radius = _useTwoRadii ? Mathf.Lerp(_radiusOne, _radiusTwo, t) : _radiusOne; | |
for (int i = 0; i < _sides; i++) | |
{ | |
var x = Mathf.Cos(angle); | |
var y = Mathf.Sin(angle); | |
circle[i] = _positions[index] + side*x* radius + up*y* radius; | |
angle += angleStep; | |
} | |
return circle; | |
} | |
} |
Hi mate, thanks for posting this script. It helped me a lot in my project!
Hi, thanks you for this script. I made a small modification that may be useful to other users, feel free to integrate it. It's a width along curve property, controllable with an animation curve.
//this may be an enum with Single, StartEnd and Curve options.
[SerializeField] bool _useCurve = false;
[SerializeField] AnimationCurve _radiusOverLength;
and then when setting up the radius:
float radius = _radiusOne;
if(_useTwoRadii) {
radius = Mathf.Lerp(_radiusOne, _radiusTwo, t);
}
else if(_useCurve){
radius = _radiusOverLength.Evaluate(t);
}
Hey thank you very much! I'll check it out and upload it to patreon as an
update! thanks a lot!
El mié, 24 may 2023 a las 21:24, Didier Surka ***@***.***>)
escribió:
… ***@***.**** commented on this gist.
------------------------------
Hi, thanks you for this script. I made a small modification that may be
useful to other users, feel free to integrate it. It's a width along curve
property, controllable with an animation curve.
//this may be an enum with Single, StartEnd and Curve options.
[SerializeField] bool _useCurve = false;
[SerializeField] AnimationCurve _radiusOverLength;
and then when setting up the radius:
float radius = _radiusOne;
if(_useTwoRadii) {
radius = Mathf.Lerp(_radiusOne, _radiusTwo, t);
}
else if(_useCurve){
radius = _radiusOverLength.Evaluate(t);
}
—
Reply to this email directly, view it on GitHub
<https://gist.github.com/mathiassoeholm/15f3eeda606e9be543165360615c8bef#gistcomment-4578869>
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AYG76OBR5U6LVCPZXN22EVDXH2YGTBFKMF2HI4TJMJ2XIZLTSKBKK5TBNR2WLJDHNFZXJJDOMFWWLK3UNBZGKYLEL52HS4DFQKSXMYLMOVS2I5DSOVS2I3TBNVS3W5DIOJSWCZC7OBQXE5DJMNUXAYLOORPWCY3UNF3GS5DZVRZXKYTKMVRXIX3UPFYGLK2HNFZXIQ3PNVWWK3TUUZ2G64DJMNZZDAVEOR4XAZNEM5UXG5FFOZQWY5LFVA2DANJUG42TKMNHORZGSZ3HMVZKMY3SMVQXIZI>
.
You are receiving this email because you commented on the thread.
Triage notifications on the go with GitHub Mobile for iOS
<https://apps.apple.com/app/apple-store/id1477376905?ct=notification-email&mt=8&pt=524675>
or Android
<https://play.google.com/store/apps/details?id=com.github.android&referrer=utm_campaign%3Dnotification-email%26utm_medium%3Demail%26utm_source%3Dgithub>
.
--
*Nicolás Soto*
*Game Developer*
***@***.*** | +56 9 5873 8467
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi, thanks for this script!
I've found it quite useful, however in my project it actually caused a memory leak.
The leak happens because when you run
GenerateMesh
Unity will not destroy the old mesh even if nothing is referencing it. My fix for this is to periodically runResources.UnloadUnusedAssets()
. It's a bit out of scope to include that in the script as it affects all objects, not just the tube meshes, but I think it's worth mentioning in case anyone else runs into the same issue.Secondly, removing the
Update
function improves the situation as the old meshes don't pile up so quickly if you don't require a new mesh every frame. SinceSetPositions
callsGenerateMesh
, I believe running onUpdate
would only be required if your tube is in world space, so I think it should be changed to