initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -0,0 +1,165 @@
using UnityEngine;
using UnityEngine.Assertions;
using UnityEngine.Splines;
using Unity.Mathematics;
using Interpolators = UnityEngine.Splines.Interpolators;
using Quaternion = UnityEngine.Quaternion;
namespace Unity.Splines.Examples
{
public class AnimateCarAlongSpline: MonoBehaviour
{
[SerializeField]
SplineContainer m_SplineContainer;
public SplineContainer splineContainer => m_SplineContainer;
[SerializeField]
Car m_CarToAnimate;
[Min(0f)]
public float m_DefaultSpeed = 10f;
[Min(0f)]
public float m_MaxSpeed = 30f;
[SerializeField]
SplineData<float> m_Speed = new SplineData<float>();
public SplineData<float> speed => m_Speed;
public Vector3 m_DefaultTilt = Vector3.up;
[SerializeField]
SplineData<float3> m_Tilt = new SplineData<float3>();
public SplineData<float3> tilt => m_Tilt;
[SerializeField]
DriftSplineData m_Drift;
public DriftSplineData driftData
{
get
{
if(m_Drift == null)
m_Drift = GetComponent<DriftSplineData>();
return m_Drift;
}
}
[SerializeField]
PointSplineData m_LookAtPoints;
public PointSplineData lookAtPoints
{
get
{
if(m_LookAtPoints == null)
m_LookAtPoints = GetComponent<PointSplineData>();
return m_LookAtPoints;
}
}
[SerializeField]
Transform m_LookTransform;
float m_CurrentOffset;
float m_CurrentSpeed;
float m_SplineLength;
Spline m_Spline;
public void Initialize()
{
//Trying to initialize either the spline container or the car
if(m_SplineContainer == null && !TryGetComponent<SplineContainer>(out m_SplineContainer))
if(m_CarToAnimate == null)
TryGetComponent<Car>(out m_CarToAnimate);
}
void Start()
{
Assert.IsNotNull(m_SplineContainer);
m_Spline = m_SplineContainer.Spline;
m_SplineLength = m_Spline.GetLength();
m_CurrentOffset = 0f;
}
void OnValidate()
{
if(m_Speed != null)
{
for(int index = 0; index < m_Speed.Count; index++)
{
var data = m_Speed[index];
//We don't want to have a value that is negative or null as it might block the simulation
if(data.Value <= 0)
{
data.Value = m_DefaultSpeed;
m_Speed[index] = data;
}
}
}
if(m_Tilt != null)
{
for(int index = 0; index < m_Tilt.Count; index++)
{
var data = m_Tilt[index];
//We don't want to have a up vector of magnitude 0
if(math.length(data.Value) == 0)
{
data.Value = m_DefaultTilt;
m_Tilt[index] = data;
}
}
}
if(lookAtPoints != null)
lookAtPoints.container = splineContainer;
if(driftData != null)
driftData.container = splineContainer;
}
void Update()
{
if(m_SplineContainer == null || m_CarToAnimate == null)
return;
m_CurrentOffset = (m_CurrentOffset + m_CurrentSpeed * Time.deltaTime / m_SplineLength) % 1f;
if (m_Speed.Count > 0)
m_CurrentSpeed = m_Speed.Evaluate(m_Spline, m_CurrentOffset, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
else
m_CurrentSpeed = m_DefaultSpeed;
var posOnSplineLocal = SplineUtility.EvaluatePosition(m_Spline, m_CurrentOffset);
var direction = SplineUtility.EvaluateTangent(m_Spline, m_CurrentOffset);
var upSplineDirection = SplineUtility.EvaluateUpVector(m_Spline, m_CurrentOffset);
var right = math.normalize(math.cross(upSplineDirection, direction));
var driftOffset = 0f;
if(driftData != null)
{
driftOffset = driftData.drift.Count == 0 ?
driftData.m_Default :
driftData.drift.Evaluate(m_Spline, m_CurrentOffset, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
}
m_CarToAnimate.transform.position = m_SplineContainer.transform.TransformPoint(posOnSplineLocal + driftOffset * right);
var up =
(m_Tilt == null || m_Tilt.Count == 0) ?
m_DefaultTilt :
(Vector3)m_Tilt.Evaluate(m_Spline, m_CurrentOffset,PathIndexUnit.Normalized, new Interpolators.LerpFloat3());
var rot = Quaternion.LookRotation(direction, upSplineDirection);
m_CarToAnimate.transform.rotation = Quaternion.LookRotation(direction, rot * up);
if (lookAtPoints != null && m_LookTransform != null && m_LookAtPoints.Count > 0)
{
var lookAtPoint = m_LookAtPoints.points.Evaluate(m_Spline, m_CurrentOffset, PathIndexUnit.Normalized, new Interpolators.LerpFloat2());
direction = math.normalize(new float3(lookAtPoint.x, 0f, lookAtPoint.y) - (float3)m_LookTransform.position);
m_LookTransform.transform.rotation = Quaternion.LookRotation(direction, up);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9eac6ceedc2724f27893c14cb52f1fc7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,32 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using Interpolators = UnityEngine.Splines.Interpolators;
namespace Unity.Splines.Examples
{
public class AnimateLookAt : MonoBehaviour
{
[SerializeField]
SplineAnimate m_SplineAnimate;
[SerializeField]
PointSplineData m_LookAtPoints;
[SerializeField]
Transform m_LookTransform;
void LateUpdate()
{
if(m_SplineAnimate == null || m_LookAtPoints == null || m_LookTransform == null)
return;
var spline = m_SplineAnimate.splineContainer.Spline;
var t = m_SplineAnimate.normalizedTime;
var lookAtPoint = m_LookAtPoints.points.Evaluate(spline, t, PathIndexUnit.Normalized, new Interpolators.SlerpFloat2());
var direction = math.normalize(new float3(lookAtPoint.x, 0f, lookAtPoint.y) - (float3)m_LookTransform.position);
m_LookTransform.transform.rotation = Quaternion.LookRotation(direction, Vector3.up);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 65b93e0cb209641b7b5a15e349da0cd0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,42 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace samples.Runtime
{
/// <summary>
/// Animate extruding a section of a spline.
/// </summary>
[RequireComponent(typeof(SplineExtrude))]
class AnimateSplineExtrude : MonoBehaviour
{
SplineExtrude m_Extrude;
[SerializeField, Range(.0001f, 2f)]
float m_Speed = .25f;
float m_Span;
[SerializeField]
bool m_RebuildExtrudeOnUpdate = true;
void Start()
{
m_Extrude = GetComponent<SplineExtrude>();
m_Span = (m_Extrude.range.y - m_Extrude.range.x) * .5f;
}
void Update()
{
bool closed = m_Extrude.spline.Closed;
float t = closed
? Time.time * m_Speed
: Mathf.Lerp(-m_Span, 1 + m_Span, math.frac(Time.time * m_Speed));
m_Extrude.range = new float2(t - m_Span, t + m_Span);
if (m_RebuildExtrudeOnUpdate)
m_Extrude.Rebuild();
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8632a8538dad4702a8e48b1a10c92ef4
timeCreated: 1637694540

View file

@ -0,0 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Splines.Examples
{
public class Car : MonoBehaviour {}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e3af2c81f946c984ead177f24f06763b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
[RequireComponent(typeof(SplineContainer))]
public class DisplayCurvatureOnSpline : MonoBehaviour
{
[Serializable]
public struct CurvatureConfig
{
public bool display;
public float time;
}
public List<CurvatureConfig> m_CurvatureTimes = new List<CurvatureConfig>();
SplineContainer m_Container;
public SplineContainer container
{
get
{
if(m_Container == null)
m_Container = GetComponent<SplineContainer>();
return m_Container;
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c5f13d731e8fcd04a85aa5137425f753
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,203 @@
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Splines;
#endif
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
using Interpolators = UnityEngine.Splines.Interpolators;
namespace Unity.Splines.Examples
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
[RequireComponent(typeof(SplineContainer), typeof(MeshRenderer), typeof(MeshFilter))]
public class LoftRoadBehaviour : MonoBehaviour
{
[SerializeField]
SplineContainer m_Spline;
[SerializeField]
int m_SegmentsPerMeter = 1;
[SerializeField]
Mesh m_Mesh;
[SerializeField]
float m_TextureScale = 1f;
WidthSplineData m_WidthData;
WidthSplineData widthData
{
get
{
if(m_WidthData == null)
m_WidthData = GetComponent<WidthSplineData>();
if(m_WidthData == null)
m_WidthData = gameObject.AddComponent<WidthSplineData>();
if(m_WidthData.container == null)
m_WidthData.container = m_Spline;
return m_WidthData;
}
}
public Spline spline
{
get
{
if (m_Spline == null)
m_Spline = GetComponent<SplineContainer>();
if (m_Spline == null)
{
Debug.LogError("Cannot loft road mesh because Spline reference is null");
return null;
}
return m_Spline.Spline;
}
}
public Mesh mesh
{
get
{
if (m_Mesh != null)
return m_Mesh;
m_Mesh = new Mesh();
GetComponent<MeshRenderer>().sharedMaterial = Resources.Load<Material>("Road");
return m_Mesh;
}
}
public int segmentsPerMeter => Mathf.Min(10, Mathf.Max(1, m_SegmentsPerMeter));
List<Vector3> m_Positions = new List<Vector3>();
List<Vector3> m_Normals = new List<Vector3>();
List<Vector2> m_Textures = new List<Vector2>();
List<int> m_Indices = new List<int>();
public void OnEnable()
{
//Avoid to point to an existing instance when duplicating the GameObject
if(m_Mesh != null)
m_Mesh = null;
if(m_WidthData == null)
m_WidthData = GetComponent<WidthSplineData>();
if(m_WidthData == null)
m_WidthData = gameObject.AddComponent<WidthSplineData>();
Loft();
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified += OnAfterSplineWasModified;
EditorSplineUtility.RegisterSplineDataChanged<float>(OnAfterSplineDataWasModified);
Undo.undoRedoPerformed += Loft;
#endif
}
public void OnDisable()
{
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified -= OnAfterSplineWasModified;
EditorSplineUtility.UnregisterSplineDataChanged<float>(OnAfterSplineDataWasModified);
Undo.undoRedoPerformed -= Loft;
#endif
if(m_Mesh != null)
#if UNITY_EDITOR
DestroyImmediate(m_Mesh);
#else
Destroy(m_Mesh);
#endif
}
void OnAfterSplineWasModified(Spline s)
{
if(s == spline)
Loft();
}
void OnAfterSplineDataWasModified(SplineData<float> splineData)
{
if (splineData == m_WidthData.width)
Loft();
}
public void Loft()
{
if (spline == null || spline.Count < 2)
return;
mesh.Clear();
float length = spline.GetLength();
if (length < 1)
return;
int segments = (int)(segmentsPerMeter * length);
int vertexCount = segments * 2, triangleCount = (spline.Closed ? segments : segments - 1) * 6;
m_Positions.Clear();
m_Normals.Clear();
m_Textures.Clear();
m_Indices.Clear();
m_Positions.Capacity = vertexCount;
m_Normals.Capacity = vertexCount;
m_Textures.Capacity = vertexCount;
m_Indices.Capacity = triangleCount;
for (int i = 0; i < segments; i++)
{
var index = i / (segments - 1f);
var control = SplineUtility.EvaluatePosition(spline, index);
var dir = SplineUtility.EvaluateTangent(spline, index);
var up = SplineUtility.EvaluateUpVector(spline, index);
var scale = transform.lossyScale;
//var tangent = math.normalize((float3)math.mul(math.cross(up, dir), new float3(1f / scale.x, 1f / scale.y, 1f / scale.z)));
var tangent = math.normalize(math.cross(up, dir)) * new float3(1f / scale.x, 1f / scale.y, 1f / scale.z);
var w = widthData.m_DefaultWidth;
if(widthData.width != null && widthData.Count > 0)
{
w = widthData.width.Evaluate(spline, index, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
w = math.clamp(w, .001f, 10000f);
}
m_Positions.Add(control - (tangent * w));
m_Positions.Add(control + (tangent * w));
m_Normals.Add(Vector3.up);
m_Normals.Add(Vector3.up);
m_Textures.Add(new Vector2(0f, index * m_TextureScale));
m_Textures.Add(new Vector2(1f, index * m_TextureScale));
}
for (int i = 0, n = 0; i < triangleCount; i += 6, n += 2)
{
m_Indices.Add((n + 2) % vertexCount);
m_Indices.Add((n + 1) % vertexCount);
m_Indices.Add((n + 0) % vertexCount);
m_Indices.Add((n + 2) % vertexCount);
m_Indices.Add((n + 3) % vertexCount);
m_Indices.Add((n + 1) % vertexCount);
}
mesh.SetVertices(m_Positions);
mesh.SetNormals(m_Normals);
mesh.SetUVs(0, m_Textures);
mesh.subMeshCount = 1;
mesh.SetIndices(m_Indices, MeshTopology.Triangles, 0);
mesh.UploadMeshData(false);
GetComponent<MeshFilter>().sharedMesh = m_Mesh;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d3f00a79918dc4a18a050534219a9842
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using Unity.Mathematics;
using Unity.Splines.Examples;
using UnityEngine;
using UnityEngine.Splines;
[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))]
public class Rollercoaster : MonoBehaviour, ISplineProvider
{
[SerializeField]
RollercoasterTrack m_Track;
[SerializeField]
Transform m_Cart;
[SerializeField]
float m_Speed = .314f;
public IEnumerable<Spline> Splines => new[] { m_Track };
void Update()
{
var trs = transform;
var t = math.frac(Time.time * m_Speed);
m_Cart.position = trs.TransformPoint(m_Track.EvaluatePosition(t));
m_Cart.rotation = Quaternion.LookRotation(trs.TransformDirection(m_Track.EvaluateTangent(t)));
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 51568c0108ad64e7f876de5f2595f260
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,125 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
#if UNITY_EDITOR
using System.IO;
using UnityEditor;
#endif
namespace Unity.Splines.Examples
{
// Example showing how to extend Spline class through inheritance.
[Serializable]
public class RollercoasterTrack : Spline
{
const string k_GeneratedMeshDirectory = "Assets/Generated/Rollercoaster";
[SerializeField, Range(.1f, 10f)]
float m_TrackWidth = 1f;
[SerializeField]
int m_TracksPerMeter = 2;
[SerializeField]
Mesh m_Mesh;
public Mesh mesh
{
get
{
if (m_Mesh != null)
return m_Mesh;
m_Mesh = new Mesh();
// In the editor it's usually convenient to save the mesh as an asset rather than generate it on the
// fly any time the scene is loaded.
#if UNITY_EDITOR
Directory.CreateDirectory(k_GeneratedMeshDirectory);
var path = AssetDatabase.GenerateUniqueAssetPath($"{k_GeneratedMeshDirectory}/Track.asset");
AssetDatabase.CreateAsset(m_Mesh, path);
AssetDatabase.ImportAsset(path);
#endif
return m_Mesh;
}
}
// When working in the editor, instead of rebuilding the mesh any time the spline changes (which can be often
// especially when moving knots), we'll schedule updates once per-frame.
#if UNITY_EDITOR
[NonSerialized]
bool m_RebuildScheduled;
#endif
protected override void OnSplineChanged()
{
base.OnSplineChanged();
#if UNITY_EDITOR
if (m_RebuildScheduled)
return;
m_RebuildScheduled = true;
EditorApplication.delayCall += () =>
{
RebuildTracks();
m_RebuildScheduled = false;
};
#else
RebuildTracks();
#endif
}
// Generate a set of ties along the spline path.
public void RebuildTracks()
{
using var spline = new NativeSpline(this);
var len = spline.GetLength();
var tieCount = (int) (len * m_TracksPerMeter);
mesh.Clear();
Vector3[] positions = new Vector3[tieCount * 4];
Vector3[] normals = new Vector3[tieCount * 4];
int[] indices = new int[tieCount * 6];
for (int i = 0; i < tieCount; i++)
{
float t = (float) i / (tieCount - 1);
var position = SplineUtility.EvaluatePosition(spline, t);
var forward = SplineUtility.EvaluateTangent(spline, t);
var tangent = (quaternion) Quaternion.LookRotation(forward);
int a = i * 4 + 0,
b = i * 4 + 1,
c = i * 4 + 2,
d = i * 4 + 3;
positions[a] = position + math.mul(tangent, new float3(-m_TrackWidth * .5f, 0f, -0.25f));
positions[b] = position + math.mul(tangent, new float3( m_TrackWidth * .5f, 0f, -0.25f));
positions[c] = position + math.mul(tangent, new float3(-m_TrackWidth * .5f, 0f, 0.25f));
positions[d] = position + math.mul(tangent, new float3( m_TrackWidth * .5f, 0f, 0.25f));
normals[a] = Vector3.up;
normals[b] = Vector3.up;
normals[c] = Vector3.up;
normals[d] = Vector3.up;
indices[i*6+0] = c;
indices[i*6+1] = b;
indices[i*6+2] = a;
indices[i*6+3] = b;
indices[i*6+4] = c;
indices[i*6+5] = d;
}
mesh.vertices = positions;
mesh.normals = normals;
mesh.subMeshCount = 1;
mesh.SetIndices(indices, MeshTopology.Triangles, 0);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0d3bd4b9fa514481a4646178f0503ea0
timeCreated: 1618498598

View file

@ -0,0 +1,71 @@
using System;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
// Visualize the nearest point on a spline to a roving sphere.
[RequireComponent(typeof(LineRenderer))]
public class ShowNearestPoint : MonoBehaviour
{
// Boundary setup for the wandering sphere
Vector3 m_Center = Vector3.zero;
float m_Size = 50f;
// Store a collection of Splines to test for nearest to our position.
SplineContainer[] m_SplineContainer;
LineRenderer m_LineRenderer;
// This GameObject will be used to visualize the nearest point on the nearest spline to this transform.
[SerializeField]
Transform m_NearestPoint;
void Start()
{
if (!TryGetComponent(out m_LineRenderer))
Debug.LogError("ShowNearestPoint requires a LineRenderer.");
m_LineRenderer.positionCount = 2;
m_SplineContainer = FindObjectsOfType<SplineContainer>();
if (m_NearestPoint == null)
Debug.LogError("Nearest Point GameObject is null");
}
void Update()
{
var position = CalculatePosition();
var nearest = new float4(0, 0, 0, float.PositiveInfinity);
foreach (var container in m_SplineContainer)
{
using var native = new NativeSpline(container.Spline, container.transform.localToWorldMatrix);
float d = SplineUtility.GetNearestPoint(native, transform.position, out float3 p, out float t);
if (d < nearest.w)
nearest = new float4(p, d);
}
m_LineRenderer.SetPosition(0, position);
m_LineRenderer.SetPosition(1, nearest.xyz);
m_NearestPoint.position = nearest.xyz;
transform.position = position;
}
Vector3 CalculatePosition()
{
float time = Time.time * .2f, time1 = time + 1;
float half = m_Size * .5f;
return m_Center + new Vector3(
Mathf.PerlinNoise(time, time) * m_Size - half,
0,
Mathf.PerlinNoise(time1, time1) * m_Size - half
);
}
void OnDrawGizmosSelected()
{
Gizmos.DrawWireCube(m_Center, new Vector3(m_Size, .1f, m_Size));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8d0206caa731d581c98dd8df314b4614
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,331 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Mathematics;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Splines;
#endif
using Interpolators = UnityEngine.Splines.Interpolators;
using Random = UnityEngine.Random;
namespace Unity.Splines.Examples
{
[ExecuteInEditMode]
[DisallowMultipleComponent]
public class SpawnWithinSplineBounds : MonoBehaviour
{
struct SpawnPoint
{
public float3 pos;
public float3 right;
public float3 up;
}
[SerializeField]
SplineContainer m_SplineContainer;
public SplineContainer splineContainer => m_SplineContainer;
[SerializeField]
Transform m_SpawnContainer;
[SerializeField]
int m_MaxIterations;
[Header("Spawning")]
[SerializeField]
List<GameObject> m_Prefabs;
[SerializeField]
float m_SpawnSpacing;
[SerializeField]
[Range(0, 1)]
float m_SpawnChance;
[SerializeField]
List<GameObject> m_BorderPrefabs;
[SerializeField]
float m_BorderSpawnSpacing;
[SerializeField]
[Range(0, 1)]
float m_BorderSpawnChance;
[SerializeField]
SplineData<float> m_SpawnBorderData;
public SplineData<float> spawnBorderData => m_SpawnBorderData;
[Header("Randomization")]
[SerializeField]
int m_RandomSeed;
[SerializeField]
Vector2 m_RotationRandomRange;
int m_Iterations;
List<Vector2> m_SplineSegments = new List<Vector2>();
List<Vector2> m_ParentPointSegments = new List<Vector2>();
const int k_SegmentsPerCurve = 10;
void OnEnable()
{
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified += OnSplineModified;
m_SpawnBorderData.changed += OnSpawnBorderDataChanged;
#endif
}
void OnDisable()
{
#if UNITY_EDITOR
EditorSplineUtility.afterSplineWasModified -= OnSplineModified;
m_SpawnBorderData.changed -= OnSpawnBorderDataChanged;
#endif
}
void OnValidate()
{
#if UNITY_EDITOR
if (m_SplineContainer != null && !EditorApplication.isPlayingOrWillChangePlaymode)
EditorApplication.delayCall += () => OnSplineModified(m_SplineContainer.Spline);
#endif
}
void CleanUp()
{
if (m_SpawnContainer == null)
return;
for (int i = m_SpawnContainer.childCount - 1; i >= 0; --i)
{
var child = m_SpawnContainer.GetChild(i);
#if UNITY_EDITOR
DestroyImmediate(child.gameObject);
#else
Destroy(child.gameObject);
#endif
}
}
void OnSplineModified(Spline spline)
{
if (spline == m_SplineContainer.Spline && m_SpawnContainer != null)
{
CleanUp();
BuildSplineSegments();
Random.InitState(m_RandomSeed);
if (m_Prefabs.Count > 0)
SpawnObjectsWithinSpline(m_SplineContainer, m_SpawnChance, m_SpawnSpacing, false);
if (m_BorderPrefabs.Count > 0)
SpawnObjectsWithinSpline(m_SplineContainer, m_BorderSpawnChance, m_BorderSpawnSpacing, true);
}
}
void SpawnObjectsWithinSpline(SplineContainer splineContainer, float spawnChance, float spawnSpacing, bool spawnOnBorder)
{
var spline = splineContainer.Spline;
var splineLen = spline.GetLength();
var points = new List<SpawnPoint>();
var splineTime = 0f;
var spawnCount = Mathf.CeilToInt(splineLen / spawnSpacing);
var splineXform = splineContainer.transform;
for (int i = 0; i < spawnCount; ++i)
{
// Here we do not need to manually transform the output vectors as all SplineContainer's evaluation methods transform the results to world space.
splineContainer.Evaluate(splineTime, out var _, out var dir, out var up);
// Spline's evaluation methods return results in spline space therfore manual transforming to world space is required.
var pos = splineXform.TransformPoint(spline.GetPointAtLinearDistance(splineTime, spawnSpacing, out splineTime));
var right = splineXform.TransformDirection(Vector3.ProjectOnPlane(math.cross(math.normalize(dir), up), up));
var spawnBorder = m_SpawnBorderData.Evaluate(spline, splineTime, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
if (spawnBorder <= spawnSpacing * 0.5f)
{
if (!spawnOnBorder)
SpawnRandomPrefab(m_Prefabs, pos, -right, up, spawnChance);
}
else if (spawnOnBorder)
SpawnRandomPrefab(m_BorderPrefabs, pos, -right, up, spawnChance);
points.Add(new SpawnPoint() { pos = pos, right = right, up = up });
}
m_Iterations = 1;
SpawnObjectsForPoints(points, spawnChance, spawnSpacing, spawnOnBorder);
}
void SpawnObjectsForPoints(List<SpawnPoint> points, float spawnChance, float spawnSpacing, bool spawnOnBorder)
{
if (m_Iterations == m_MaxIterations)
return;
// Backup parent points
var parentPoints = new List<SpawnPoint>(points);
// Offset all child points along right vector
for (int i = 0; i < points.Count; i++)
{
var splinePoint = points[i];
var nextIdx = (i == points.Count - 1 ? 0 : i + 1);
var nextPoint = points[nextIdx];
var right = (float3) Vector3.Slerp(splinePoint.right, nextPoint.right, 0.5f);
var up = (float3) Vector3.Slerp(splinePoint.up, nextPoint.up, 0.5f);
var pos = math.lerp(splinePoint.pos, nextPoint.pos, 0.5f);
splinePoint.pos = pos + right * spawnSpacing;
splinePoint.right = right;
splinePoint.up = up;
points[i] = splinePoint;
}
var pointsToRemove = new SortedSet<int>();
var spawnedPoints = new List<SpawnPoint>();
// Check if point should be discard - spawn otherwise
for (int i = 0; i < points.Count; ++i)
{
var discardPoint = false;
var pointWithinBorder = false;
var pointSplineSpace = m_SplineContainer.transform.InverseTransformPoint(points[i].pos);
// Check against border
var dist = SplineUtility.GetNearestPoint(m_SplineContainer.Spline, pointSplineSpace, out var _, out var splineTime);
var spawnOffset = m_SpawnBorderData.Evaluate(m_SplineContainer.Spline, splineTime, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
if (dist < spawnOffset)
pointWithinBorder = true;
// Check against child points
for (int spawnedPointIdx = spawnedPoints.Count - 1; spawnedPointIdx >= 0; --spawnedPointIdx)
{
if (math.length(points[i].pos - spawnedPoints[spawnedPointIdx].pos) < spawnSpacing)
{
discardPoint = true;
pointsToRemove.Add(i);
break;
}
}
// Check against parent points
if (!discardPoint)
{
var nextIdx = i == points.Count - 1 ? 0 : i + 1;
while (i != nextIdx)
{
if (math.length(points[i].pos - parentPoints[nextIdx].pos) < spawnSpacing * 0.9f)
{
discardPoint = true;
pointsToRemove.Add(i);
break;
}
nextIdx = nextIdx == points.Count - 1 ? 0 : nextIdx + 1;
}
}
// Ensure point is within parent points bounds
if (!discardPoint)
{
m_ParentPointSegments.Clear();
foreach (var parentPoint in parentPoints)
m_ParentPointSegments.Add(new Vector2(parentPoint.pos.x, parentPoint.pos.z));
discardPoint = !PointInsidePolygon(new Vector2(points[i].pos.x, points[i].pos.z), m_ParentPointSegments);
}
// Ensure point is roughly within spline bounds
if (!discardPoint)
discardPoint = !PointInsidePolygon(new Vector2(pointSplineSpace.x, pointSplineSpace.z), m_SplineSegments);
if (!discardPoint)
{
if (!pointWithinBorder)
{
if (!spawnOnBorder)
SpawnRandomPrefab(m_Prefabs, points[i].pos, -points[i].right, points[i].up, spawnChance);
}
else if (spawnOnBorder)
SpawnRandomPrefab(m_BorderPrefabs, points[i].pos, -points[i].right, points[i].up, spawnChance);
spawnedPoints.Add(points[i]);
}
}
m_Iterations++;
foreach (var point in pointsToRemove.Reverse())
points.RemoveAt(point);
if (points.Count == 0)
return;
SpawnObjectsForPoints(points, spawnChance, spawnSpacing, spawnOnBorder);
}
void SpawnRandomPrefab(List<GameObject> prefabs, Vector3 position, Vector3 forward, Vector3 up, float spawnChance)
{
if (Random.Range(0f, 1f) > spawnChance)
return;
var prefab = prefabs[Random.Range(0, prefabs.Count)];
var go = Instantiate(prefab, position, quaternion.identity);
go.transform.rotation = Quaternion.LookRotation(forward, up) * Quaternion.AngleAxis(Random.Range(m_RotationRandomRange.x, m_RotationRandomRange.y), Vector3.up);
go.transform.SetParent(m_SpawnContainer, true);
}
void OnSpawnBorderDataChanged()
{
OnSplineModified(m_SplineContainer.Spline);
}
void BuildSplineSegments()
{
m_SplineSegments.Clear();
var spline = m_SplineContainer.Spline;
var curveCount = spline.Closed ? spline.Count : spline.Count - 1;
var stepSize = 1f / k_SegmentsPerCurve;
for (int curveIndex = 0; curveIndex < curveCount; ++curveIndex)
{
for (int step = 0; step < k_SegmentsPerCurve; ++step)
{
var splineTime = spline.CurveToSplineT(curveIndex + step * stepSize);
var pos = spline.EvaluatePosition(splineTime);
m_SplineSegments.Add(new Vector2(pos.x, pos.z));
}
}
}
bool PointInsidePolygon(Vector2 point, List<Vector2> polygon)
{
Vector2 p1, p2;
p1 = polygon[0];
var counter = 0;
for (int i = 1; i <= polygon.Count; i++)
{
p2 = polygon[i % polygon.Count];
if (point.y > Mathf.Min(p1.y, p2.y))
{
if (point.y <= Mathf.Max(p1.y, p2.y))
{
if (point.x <= Mathf.Max(p1.x, p2.x))
{
if (p1.y != p2.y)
{
var xinters = (point.y - p1.y) * (p2.x - p1.x) / (p2.y - p1.y) + p1.x;
if (p1.x == p2.x || point.x <= xinters)
counter++;
}
}
}
}
p1 = p2;
}
if (counter % 2 == 0)
return false;
else
return true;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2e0318f4ace494445a245336e74fcd88
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 497b34923fb3579478af67bfb5a82674
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,27 @@
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
public class DriftSplineData : MonoBehaviour
{
public float m_Default = 0f;
[SerializeField]
SplineData<float> m_Drift;
public SplineData<float> drift => m_Drift;
SplineContainer m_SplineContainer;
public SplineContainer container
{
get
{
if(m_SplineContainer == null)
m_SplineContainer = GetComponent<SplineContainer>();
return m_SplineContainer;
}
set => m_SplineContainer = value;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: cd08d575c1c88a646835fb7c516cfa72
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,27 @@
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
public class PointSplineData : MonoBehaviour
{
[SerializeField]
SplineData<float2> m_Points;
public SplineData<float2> points => m_Points;
public int Count => m_Points.Count;
SplineContainer m_SplineContainer;
public SplineContainer container
{
get
{
if(m_SplineContainer == null)
m_SplineContainer = GetComponent<SplineContainer>();
return m_SplineContainer;
}
set => m_SplineContainer = value;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 18b78468053521b4dbb5d1a7dba64532
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,31 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
public class WidthSplineData : MonoBehaviour
{
public float m_DefaultWidth = 1f;
[SerializeField]
SplineData<float> m_Width;
public SplineData<float> width => m_Width;
public int Count => m_Width.Count;
SplineContainer m_SplineContainer;
public SplineContainer container
{
get
{
if(m_SplineContainer == null)
m_SplineContainer = GetComponent<SplineContainer>();
return m_SplineContainer;
}
set => m_SplineContainer = value;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a787722e962040bca5a786da5aae63f3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,35 @@
using System.Linq;
using Unity.Mathematics;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples.Editor
{
[RequireComponent(typeof(SplineContainer))]
class SplineOscillator : MonoBehaviour
{
Spline m_Spline;
BezierKnot[] m_Origins;
[SerializeField, Range(.1f, 10f)]
float m_Speed = 3f;
[SerializeField, Range(1f, 10f)]
float m_Frequency = 3.14f;
void Start()
{
m_Spline = GetComponent<SplineContainer>().Spline;
m_Origins = m_Spline.Knots.ToArray();
}
void Update()
{
for (int i = 0, c = m_Spline.Count; i < c; ++i)
{
var offset = i / (c - 1f) * m_Frequency;
m_Spline[i] = m_Origins[i] + math.cos((Time.time + offset) * m_Speed) * new float3(0, 1, 0);
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3fcd72b7e265696c4a96291318699084
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,47 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
[RequireComponent(typeof(LineRenderer), typeof(SplineContainer))]
public class SplineRenderer : MonoBehaviour
{
Spline m_Spline;
LineRenderer m_Line;
bool m_Dirty;
Vector3[] m_Points;
[SerializeField, Range(16, 512)]
int m_Segments = 128;
void Awake()
{
m_Spline = GetComponent<SplineContainer>().Spline;
m_Line = GetComponent<LineRenderer>();
m_Spline.changed += () => m_Dirty = true;
}
void Update()
{
// It's nice to be able to see resolution changes at runtime
if (m_Points?.Length != m_Segments)
{
m_Dirty = true;
m_Points = new Vector3[m_Segments];
m_Line.loop = m_Spline.Closed;
m_Line.positionCount = m_Segments;
}
if (!m_Dirty)
return;
m_Dirty = false;
for (int i = 0; i < m_Segments; i++)
m_Points[i] = m_Spline.EvaluatePosition(i / (m_Segments - 1f));
m_Line.SetPositions(m_Points);
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9dd4cf612254c08bf888d5bca6bb2d5
timeCreated: 1628867781

View file

@ -0,0 +1,90 @@
using System;
using UnityEngine;
using UnityEngine.Splines;
namespace Unity.Splines.Examples
{
/// <summary>
/// A simple example showing how to pass Spline data to the GPU using SplineComputeBufferScope.
/// </summary>
[RequireComponent(typeof(LineRenderer), typeof(SplineContainer))]
public class SplineRendererCompute : MonoBehaviour
{
// Use with Shader/InterpolateSpline.compute
[SerializeField]
ComputeShader m_ComputeShader;
[SerializeField, Range(16, 512)]
int m_Segments = 128;
Spline m_Spline;
LineRenderer m_Line;
bool m_Dirty;
SplineComputeBufferScope<Spline> m_SplineBuffers;
Vector3[] m_Positions;
ComputeBuffer m_PositionsBuffer;
int m_GetPositionsKernel;
void Awake()
{
m_Spline = GetComponent<SplineContainer>().Spline;
// Set up the LineRenderer
m_Line = GetComponent<LineRenderer>();
m_Line.positionCount = m_Segments;
m_GetPositionsKernel = m_ComputeShader.FindKernel("GetPositions");
// Set up the spline evaluation compute shader. We'll use SplineComputeBufferScope to simplify the process.
// Note that SplineComputeBufferScope is optional, you can manage the Curve, Lengths, and Info properties
// yourself if preferred.
m_SplineBuffers = new SplineComputeBufferScope<Spline>(m_Spline);
m_SplineBuffers.Bind(m_ComputeShader, m_GetPositionsKernel, "info", "curves", "curveLengths");
// Set the compute shader properties necessary for accessing spline information. Most Spline functions in
// Spline.cginc require the info, curves, and curve length properties. This is equivalent to:
// m_ComputeShader.SetVector("info", m_SplineBuffers.Info);
// m_ComputeShader.SetBuffer(m_GetPositionsKernel, "curves", m_SplineBuffers.Curves);
// m_ComputeShader.SetBuffer(m_GetPositionsKernel, "curveLengths", m_SplineBuffers.CurveLengths);
m_SplineBuffers.Upload();
// m_Positions will be used to read back evaluated positions from the GPU
m_Positions = new Vector3[m_Segments];
// Set up our input and readback buffers. In this example we'll evaluate a set of positions along the spline
m_PositionsBuffer = new ComputeBuffer(m_Segments, sizeof(float) * 3);
m_PositionsBuffer.SetData(m_Positions);
m_ComputeShader.SetBuffer(m_GetPositionsKernel, "positions", m_PositionsBuffer);
m_ComputeShader.SetFloat("positionsCount", m_Segments);
// Subscribe to Spline.changed to be notified when the spline is modified
m_Spline.changed += () => m_Dirty = true;
m_Dirty = true;
}
void OnDestroy()
{
m_PositionsBuffer?.Dispose();
m_SplineBuffers.Dispose();
}
void Update()
{
if (!m_Dirty)
return;
// Once initialized, call SplineComputeBufferScope.Upload() to update the GPU copies of spline data. This
// is only necessary here because we're constantly updating the Spline in this example. If the Spline is
// static, there is no need to call Upload every frame.
m_SplineBuffers.Upload();
m_ComputeShader.GetKernelThreadGroupSizes(m_GetPositionsKernel, out var threadSize, out _, out _);
m_ComputeShader.Dispatch(m_GetPositionsKernel, (int) threadSize, 1, 1);
m_PositionsBuffer.GetData(m_Positions);
m_Line.loop = m_Spline.Closed;
m_Line.SetPositions(m_Positions);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fcea5d0b9b5209031bf47c34bf1eb238
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
{
"name": "Unity.Splines.Examples",
"rootNamespace": "",
"references": [
"GUID:21d1eb854b91ade49bc69a263d12bee2",
"GUID:d8b63aba1907145bea998dd612889d6b",
"GUID:e43142fc3fec6554980dde6126631f1d"
],
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 9f08613db0663446b876a5eae626aa45
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: