first commit
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Assertions;
|
||||
using UnityEngine.Splines;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine.Serialization;
|
||||
using Interpolators = UnityEngine.Splines.Interpolators;
|
||||
using Quaternion = UnityEngine.Quaternion;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class AnimateCarAlongSpline : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
SplineContainer m_SplineContainer;
|
||||
[Obsolete("Use Container instead.", false)]
|
||||
public SplineContainer splineContainer => Container;
|
||||
public SplineContainer Container => m_SplineContainer;
|
||||
|
||||
[SerializeField]
|
||||
Car m_CarToAnimate;
|
||||
|
||||
[HideInInspector]
|
||||
[Obsolete("No longer used.", false)]
|
||||
public float m_DefaultSpeed;
|
||||
|
||||
[HideInInspector]
|
||||
[Obsolete("No longer used.", false)]
|
||||
public Vector3 m_DefaultTilt;
|
||||
|
||||
[HideInInspector]
|
||||
[Obsolete("Use MaxSpeed instead.", false)]
|
||||
public float m_MaxSpeed = 40f;
|
||||
[FormerlySerializedAs("m_MaxSpeed")]
|
||||
[Min(0f)]
|
||||
public float MaxSpeed = 40f;
|
||||
|
||||
[SerializeField]
|
||||
SplineData<float> m_Speed = new SplineData<float>();
|
||||
[Obsolete("Use Speed instead.", false)]
|
||||
public SplineData<float> speed => Speed;
|
||||
public SplineData<float> Speed => m_Speed;
|
||||
|
||||
[SerializeField]
|
||||
SplineData<float3> m_Tilt = new SplineData<float3>();
|
||||
[Obsolete("Use Tilt instead.", false)]
|
||||
public SplineData<float3> tilt => Tilt;
|
||||
public SplineData<float3> Tilt => m_Tilt;
|
||||
|
||||
[SerializeField]
|
||||
DriftSplineData m_Drift;
|
||||
|
||||
[Obsolete("Use DriftData instead.", false)]
|
||||
public DriftSplineData driftData => DriftData;
|
||||
public DriftSplineData DriftData
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Drift == null)
|
||||
m_Drift = GetComponent<DriftSplineData>();
|
||||
|
||||
return m_Drift;
|
||||
}
|
||||
}
|
||||
|
||||
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 = Mathf.Max(0f, m_Speed.DefaultValue);
|
||||
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_Tilt.DefaultValue;
|
||||
m_Tilt[index] = data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DriftData != null)
|
||||
DriftData.Container = Container;
|
||||
}
|
||||
|
||||
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_Speed.DefaultValue;
|
||||
|
||||
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.Drift.DefaultValue :
|
||||
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.Count == 0) ?
|
||||
m_Tilt.DefaultValue :
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9eac6ceedc2724f27893c14cb52f1fc7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
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 allClosed = true;
|
||||
foreach (var spline in m_Extrude.Splines)
|
||||
if (!spline.Closed)
|
||||
{
|
||||
allClosed = false;
|
||||
break;
|
||||
}
|
||||
|
||||
float t = allClosed
|
||||
? 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8632a8538dad4702a8e48b1a10c92ef4
|
||||
timeCreated: 1637694540
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class CameraPathExample : MonoBehaviour
|
||||
{
|
||||
public SplineContainer container;
|
||||
|
||||
[SerializeField]
|
||||
float speed = 0.01f;
|
||||
|
||||
SplinePath cameraTrack;
|
||||
|
||||
void Start()
|
||||
{
|
||||
cameraTrack = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(0, 6),
|
||||
container.transform.localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[1], new SplineRange(0, 6),
|
||||
container.transform.localToWorldMatrix)
|
||||
});
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
cameraTrack.Evaluate(math.frac(speed * Time.time), out var pos, out var right, out var up);
|
||||
Vector3 forward = Vector3.Cross(right, up);
|
||||
transform.position = pos;
|
||||
transform.LookAt((Vector3) pos + forward);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7d0d2e4d7f0c0a40ab38f9ba272a7e9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class Car : MonoBehaviour {}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3af2c81f946c984ead177f24f06763b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class DriftSplineData : MonoBehaviour
|
||||
{
|
||||
[HideInInspector]
|
||||
[Obsolete("No longer used.", false)]
|
||||
public float m_Default;
|
||||
|
||||
[SerializeField]
|
||||
SplineData<float> m_Drift = new SplineData<float>();
|
||||
|
||||
[Obsolete("Use Drift instead.", false)]
|
||||
public SplineData<float> drift => Drift;
|
||||
public SplineData<float> Drift => m_Drift;
|
||||
|
||||
SplineContainer m_SplineContainer;
|
||||
|
||||
[Obsolete("Use Container instead.", false)]
|
||||
public SplineContainer container => Container;
|
||||
public SplineContainer Container
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_SplineContainer == null)
|
||||
m_SplineContainer = GetComponent<SplineContainer>();
|
||||
return m_SplineContainer;
|
||||
}
|
||||
set => m_SplineContainer = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cd08d575c1c88a646835fb7c516cfa72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class HighwayExample : MonoBehaviour
|
||||
{
|
||||
public SplineContainer container;
|
||||
|
||||
[SerializeField]
|
||||
float speed = 0.1f;
|
||||
|
||||
SplinePath[] paths = new SplinePath[4];
|
||||
float t = 0f;
|
||||
|
||||
IEnumerator CarPathCoroutine()
|
||||
{
|
||||
for(int n = 0; ; ++n)
|
||||
{
|
||||
t = 0f;
|
||||
var path = paths[n % 4];
|
||||
|
||||
while (t <= 1f)
|
||||
{
|
||||
var pos = path.EvaluatePosition(t);
|
||||
var direction = path.EvaluateTangent(t);
|
||||
transform.position = pos;
|
||||
transform.LookAt(pos + direction);
|
||||
t += speed * Time.deltaTime;
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Start()
|
||||
{
|
||||
var localToWorldMatrix = container.transform.localToWorldMatrix;
|
||||
paths[0] = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(0, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[3], new SplineRange(0, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[1], new SplineRange(1, 2), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[2], new SplineRange(2, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(3, 3), localToWorldMatrix)
|
||||
});
|
||||
|
||||
paths[1] = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(0, 2), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[2], new SplineRange(0, 5), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(3, 3), localToWorldMatrix)
|
||||
});
|
||||
|
||||
paths[2] = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(0, 2), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[2], new SplineRange(0, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[1], new SplineRange(2, -3), localToWorldMatrix),
|
||||
});
|
||||
|
||||
paths[3] = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(0, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[3], new SplineRange(0, 3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[1], new SplineRange(1, 2), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[2], new SplineRange(2, -3), localToWorldMatrix),
|
||||
new SplineSlice<Spline>(container.Splines[0], new SplineRange(1, -2), localToWorldMatrix),
|
||||
});
|
||||
|
||||
StartCoroutine(CarPathCoroutine());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 37f15b3d02f03eb49af32bd9e33df72e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Splines;
|
||||
#endif
|
||||
|
||||
// An example showing how to use embedded SplineData on the Spline class. Additionally showcases how to respond to
|
||||
// changes in the Inspector.
|
||||
namespace UnityEngine.Splines.Examples
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
[CustomEditor(typeof(ItemPickup))]
|
||||
class ItemPickupEditor : Editor
|
||||
{
|
||||
void OnEnable()
|
||||
{
|
||||
EditorSplineUtility.AfterSplineWasModified += OnAfterSplineModified;
|
||||
EditorSplineUtility.RegisterSplineDataChanged<Object>(OnAfterSplineDataWasModified);
|
||||
Undo.undoRedoPerformed += InstantiatePrefabs;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
EditorSplineUtility.AfterSplineWasModified -= OnAfterSplineModified;
|
||||
EditorSplineUtility.UnregisterSplineDataChanged<Object>(OnAfterSplineDataWasModified);
|
||||
Undo.undoRedoPerformed -= InstantiatePrefabs;
|
||||
}
|
||||
|
||||
void OnAfterSplineModified(Spline _) => InstantiatePrefabs();
|
||||
|
||||
void InstantiatePrefabs()
|
||||
{
|
||||
if (target is ItemPickup pickup)
|
||||
pickup.Instantiate();
|
||||
}
|
||||
|
||||
void OnAfterSplineDataWasModified(SplineData<Object> splineData)
|
||||
{
|
||||
InstantiatePrefabs();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
base.OnInspectorGUI();
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
InstantiatePrefabs();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[RequireComponent(typeof(SplineContainer))]
|
||||
public class ItemPickup : MonoBehaviour
|
||||
{
|
||||
public const string PickupDataKey = "pickups";
|
||||
|
||||
// EmbeddedSplineData is a convenience class that wraps all the fields necessary to access SplineData embedded
|
||||
// in a Spline class and provides a custom property drawer.
|
||||
// If `Container` and `SplineIndex` are omitted, it is assumed that the referenced Spline is on the first
|
||||
// SplineContainer component found on the same GameObject, at index 0.
|
||||
// I.e., gameObject.Component<SplineContainer>().Splines[0].
|
||||
// In this example we specify that only the key should be shown in the Inspector.
|
||||
[SerializeField, EmbeddedSplineDataFields(EmbeddedSplineDataField.Key | EmbeddedSplineDataField.SplineIndex)]
|
||||
EmbeddedSplineData m_Pickups = new EmbeddedSplineData()
|
||||
{
|
||||
Key = PickupDataKey,
|
||||
Type = EmbeddedSplineDataType.Object
|
||||
};
|
||||
|
||||
public void Instantiate()
|
||||
{
|
||||
m_Pickups.Container = GetComponent<SplineContainer>();
|
||||
|
||||
for (int i = transform.childCount - 1; i > -1; --i)
|
||||
DestroyImmediate(transform.GetChild(i).gameObject);
|
||||
|
||||
if (!m_Pickups.TryGetSpline(out var spline))
|
||||
return;
|
||||
|
||||
var parent = transform;
|
||||
|
||||
// SplineData embedded in a Spline directly is accessed through a key value pair interface. You could also use TryGetObjectData
|
||||
// if you do not want to create a new SplineData entry. This function
|
||||
// will create a new SplineData object if one does not exist already.
|
||||
if (!m_Pickups.TryGetObjectData(out var prefabs))
|
||||
return;
|
||||
|
||||
foreach (var key in prefabs)
|
||||
{
|
||||
if (key.Value == null)
|
||||
continue;
|
||||
|
||||
float t = spline.ConvertIndexUnit(key.Index, prefabs.PathIndexUnit, PathIndexUnit.Normalized);
|
||||
m_Pickups.Container.Evaluate(m_Pickups.SplineIndex, t, out var position, out var tangent, out var normal);
|
||||
var rotation = Vector3.SqrMagnitude(tangent) < float.Epsilon || Vector3.SqrMagnitude(normal) < float.Epsilon
|
||||
? Quaternion.identity
|
||||
: Quaternion.LookRotation(tangent, normal);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
if (PrefabUtility.InstantiatePrefab(key.Value, parent) is GameObject instance)
|
||||
#else
|
||||
if (Instantiate(key.Value, parent) is GameObject instance)
|
||||
#endif
|
||||
instance.transform.SetPositionAndRotation(position, rotation);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
Selection.activeObject = gameObject;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5e021ffcfaf47d28c0e5b0e01334c09
|
||||
timeCreated: 1673290694
|
||||
@@ -0,0 +1,341 @@
|
||||
#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]
|
||||
List<SplineData<float>> m_Widths = new List<SplineData<float>>();
|
||||
|
||||
public List<SplineData<float>> Widths
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var width in m_Widths)
|
||||
{
|
||||
if (width.DefaultValue == 0)
|
||||
width.DefaultValue = 1f;
|
||||
}
|
||||
|
||||
return m_Widths;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
SplineContainer m_Spline;
|
||||
|
||||
public SplineContainer Container
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Spline == null)
|
||||
m_Spline = GetComponent<SplineContainer>();
|
||||
|
||||
return m_Spline;
|
||||
}
|
||||
set => m_Spline = value;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
int m_SegmentsPerMeter = 1;
|
||||
|
||||
[SerializeField]
|
||||
Mesh m_Mesh;
|
||||
|
||||
[SerializeField]
|
||||
float m_TextureScale = 1f;
|
||||
|
||||
public IReadOnlyList<Spline> splines => LoftSplines;
|
||||
|
||||
public IReadOnlyList<Spline> LoftSplines
|
||||
{
|
||||
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.Splines;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use LoftMesh instead.", false)]
|
||||
public Mesh mesh => LoftMesh;
|
||||
public Mesh LoftMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Mesh != null)
|
||||
return m_Mesh;
|
||||
|
||||
m_Mesh = new Mesh();
|
||||
GetComponent<MeshRenderer>().sharedMaterial = Resources.Load<Material>("Road");
|
||||
return m_Mesh;
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("Use SegmentsPerMeter instead.", false)]
|
||||
public int segmentsPerMeter => SegmentsPerMeter;
|
||||
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>();
|
||||
bool m_LoftRoadsRequested = false;
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_LoftRoadsRequested)
|
||||
{
|
||||
LoftAllRoads();
|
||||
m_LoftRoadsRequested = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
// Avoid to point to an existing instance when duplicating the GameObject
|
||||
if (m_Mesh != null)
|
||||
m_Mesh = null;
|
||||
|
||||
if (m_Spline == null)
|
||||
m_Spline = GetComponent<SplineContainer>();
|
||||
|
||||
LoftAllRoads();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
EditorSplineUtility.AfterSplineWasModified += OnAfterSplineWasModified;
|
||||
EditorSplineUtility.RegisterSplineDataChanged<float>(OnAfterSplineDataWasModified);
|
||||
Undo.undoRedoPerformed += LoftAllRoads;
|
||||
#endif
|
||||
|
||||
SplineContainer.SplineAdded += OnSplineContainerAdded;
|
||||
SplineContainer.SplineRemoved += OnSplineContainerRemoved;
|
||||
SplineContainer.SplineReordered += OnSplineContainerReordered;
|
||||
Spline.Changed += OnSplineChanged;
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EditorSplineUtility.AfterSplineWasModified -= OnAfterSplineWasModified;
|
||||
EditorSplineUtility.UnregisterSplineDataChanged<float>(OnAfterSplineDataWasModified);
|
||||
Undo.undoRedoPerformed -= LoftAllRoads;
|
||||
#endif
|
||||
|
||||
if (m_Mesh != null)
|
||||
#if UNITY_EDITOR
|
||||
DestroyImmediate(m_Mesh);
|
||||
#else
|
||||
Destroy(m_Mesh);
|
||||
#endif
|
||||
|
||||
SplineContainer.SplineAdded -= OnSplineContainerAdded;
|
||||
SplineContainer.SplineRemoved -= OnSplineContainerRemoved;
|
||||
SplineContainer.SplineReordered -= OnSplineContainerReordered;
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnSplineContainerAdded(SplineContainer container, int index)
|
||||
{
|
||||
if (container != m_Spline)
|
||||
return;
|
||||
|
||||
if (m_Widths.Count < LoftSplines.Count)
|
||||
{
|
||||
var delta = LoftSplines.Count - m_Widths.Count;
|
||||
for (var i = 0; i < delta; i++)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Undo.RecordObject(this, "Modifying Widths SplineData");
|
||||
#endif
|
||||
m_Widths.Add(new SplineData<float>() { DefaultValue = 1f });
|
||||
}
|
||||
}
|
||||
|
||||
LoftAllRoads();
|
||||
}
|
||||
|
||||
void OnSplineContainerRemoved(SplineContainer container, int index)
|
||||
{
|
||||
if (container != m_Spline)
|
||||
return;
|
||||
|
||||
if (index < m_Widths.Count)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Undo.RecordObject(this, "Modifying Widths SplineData");
|
||||
#endif
|
||||
m_Widths.RemoveAt(index);
|
||||
}
|
||||
|
||||
LoftAllRoads();
|
||||
}
|
||||
|
||||
void OnSplineContainerReordered(SplineContainer container, int previousIndex, int newIndex)
|
||||
{
|
||||
if (container != m_Spline)
|
||||
return;
|
||||
|
||||
LoftAllRoads();
|
||||
}
|
||||
|
||||
void OnAfterSplineWasModified(Spline s)
|
||||
{
|
||||
if (LoftSplines == null)
|
||||
return;
|
||||
|
||||
foreach (var spline in LoftSplines)
|
||||
{
|
||||
if (s == spline)
|
||||
{
|
||||
m_LoftRoadsRequested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modification)
|
||||
{
|
||||
OnAfterSplineWasModified(spline);
|
||||
}
|
||||
|
||||
void OnAfterSplineDataWasModified(SplineData<float> splineData)
|
||||
{
|
||||
foreach (var width in m_Widths)
|
||||
{
|
||||
if (splineData == width)
|
||||
{
|
||||
m_LoftRoadsRequested = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void LoftAllRoads()
|
||||
{
|
||||
LoftMesh.Clear();
|
||||
m_Positions.Clear();
|
||||
m_Normals.Clear();
|
||||
m_Textures.Clear();
|
||||
m_Indices.Clear();
|
||||
m_Positions.Capacity = 0;
|
||||
m_Normals.Capacity = 0;
|
||||
m_Textures.Capacity = 0;
|
||||
m_Indices.Capacity = 0;
|
||||
|
||||
for (var i = 0; i < LoftSplines.Count; i++)
|
||||
Loft(LoftSplines[i], i);
|
||||
|
||||
LoftMesh.SetVertices(m_Positions);
|
||||
LoftMesh.SetNormals(m_Normals);
|
||||
LoftMesh.SetUVs(0, m_Textures);
|
||||
LoftMesh.subMeshCount = 1;
|
||||
LoftMesh.SetIndices(m_Indices, MeshTopology.Triangles, 0);
|
||||
LoftMesh.UploadMeshData(false);
|
||||
|
||||
GetComponent<MeshFilter>().sharedMesh = m_Mesh;
|
||||
}
|
||||
|
||||
public void Loft(Spline spline, int widthDataIndex)
|
||||
{
|
||||
if (spline == null || spline.Count < 2)
|
||||
return;
|
||||
|
||||
LoftMesh.Clear();
|
||||
|
||||
float length = spline.GetLength();
|
||||
|
||||
if (length <= 0.001f)
|
||||
return;
|
||||
|
||||
var segmentsPerLength = SegmentsPerMeter * length;
|
||||
var segments = Mathf.CeilToInt(segmentsPerLength);
|
||||
var segmentStepT = (1f / SegmentsPerMeter) / length;
|
||||
var steps = segments + 1;
|
||||
var vertexCount = steps * 2;
|
||||
var triangleCount = segments * 6;
|
||||
var prevVertexCount = m_Positions.Count;
|
||||
|
||||
m_Positions.Capacity += vertexCount;
|
||||
m_Normals.Capacity += vertexCount;
|
||||
m_Textures.Capacity += vertexCount;
|
||||
m_Indices.Capacity += triangleCount;
|
||||
|
||||
var t = 0f;
|
||||
for (int i = 0; i < steps; i++)
|
||||
{
|
||||
SplineUtility.Evaluate(spline, t, out var pos, out var dir, out var up);
|
||||
|
||||
// If dir evaluates to zero (linear or broken zero length tangents?)
|
||||
// then attempt to advance forward by a small amount and build direction to that point
|
||||
if (math.length(dir) == 0)
|
||||
{
|
||||
var nextPos = spline.GetPointAtLinearDistance(t, 0.01f, out _);
|
||||
dir = math.normalizesafe(nextPos - pos);
|
||||
|
||||
if (math.length(dir) == 0)
|
||||
{
|
||||
nextPos = spline.GetPointAtLinearDistance(t, -0.01f, out _);
|
||||
dir = -math.normalizesafe(nextPos - pos);
|
||||
}
|
||||
|
||||
if (math.length(dir) == 0)
|
||||
dir = new float3(0, 0, 1);
|
||||
}
|
||||
|
||||
var scale = transform.lossyScale;
|
||||
var tangent = math.normalizesafe(math.cross(up, dir)) * new float3(1f / scale.x, 1f / scale.y, 1f / scale.z);
|
||||
|
||||
var w = 1f;
|
||||
if (widthDataIndex < m_Widths.Count)
|
||||
{
|
||||
w = m_Widths[widthDataIndex].DefaultValue;
|
||||
if (m_Widths[widthDataIndex] != null && m_Widths[widthDataIndex].Count > 0)
|
||||
{
|
||||
w = m_Widths[widthDataIndex].Evaluate(spline, t, PathIndexUnit.Normalized, new Interpolators.LerpFloat());
|
||||
w = math.clamp(w, .001f, 10000f);
|
||||
}
|
||||
}
|
||||
|
||||
m_Positions.Add(pos - (tangent * w));
|
||||
m_Positions.Add(pos + (tangent * w));
|
||||
m_Normals.Add(up);
|
||||
m_Normals.Add(up);
|
||||
m_Textures.Add(new Vector2(0f, t * m_TextureScale));
|
||||
m_Textures.Add(new Vector2(1f, t * m_TextureScale));
|
||||
|
||||
t = math.min(1f, t + segmentStepT);
|
||||
}
|
||||
|
||||
for (int i = 0, n = prevVertexCount; i < triangleCount; i += 6, n += 2)
|
||||
{
|
||||
m_Indices.Add((n + 2) % (prevVertexCount + vertexCount));
|
||||
m_Indices.Add((n + 1) % (prevVertexCount + vertexCount));
|
||||
m_Indices.Add((n + 0) % (prevVertexCount + vertexCount));
|
||||
m_Indices.Add((n + 2) % (prevVertexCount + vertexCount));
|
||||
m_Indices.Add((n + 3) % (prevVertexCount + vertexCount));
|
||||
m_Indices.Add((n + 1) % (prevVertexCount + vertexCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3f00a79918dc4a18a050534219a9842
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
public class NPCSplinePathExample : MonoBehaviour
|
||||
{
|
||||
|
||||
public GameObject npc;
|
||||
public SplineContainer container1;
|
||||
public SplineContainer container2;
|
||||
public Vector3 offsetFromPath;
|
||||
|
||||
SplinePath path;
|
||||
float t = 0f;
|
||||
|
||||
void Start()
|
||||
{
|
||||
var container1Transform = container1.transform.localToWorldMatrix;
|
||||
var container2Transform = container2.transform.localToWorldMatrix;
|
||||
// Create a SplinePath from a subset of Splines
|
||||
path = new SplinePath(new[]
|
||||
{
|
||||
new SplineSlice<Spline>(container1.Splines[1], new SplineRange(0, 4), container1Transform),
|
||||
new SplineSlice<Spline>(container1.Splines[2], new SplineRange(0, 4), container1Transform),
|
||||
new SplineSlice<Spline>(container1.Splines[0], new SplineRange(4, -5), container1Transform),
|
||||
new SplineSlice<Spline>(container1.Splines[1], new SplineRange(0, 2), container1Transform),
|
||||
new SplineSlice<Spline>(container1.Splines[3], new SplineRange(0, 2), container1Transform),
|
||||
new SplineSlice<Spline>(container2.Splines[0], new SplineRange(3, -4), container2Transform)
|
||||
});
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
Vector3 pos = path.EvaluatePosition(t);
|
||||
npc.transform.position = pos + offsetFromPath;
|
||||
|
||||
t += 0.05f * Time.deltaTime;
|
||||
if (t > 1f) t = 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b80039d79eca67143b32a5c83ed395fb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,129 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
/// <summary>
|
||||
/// This sample demonstrates how to create a spline from a collection of points drawn by the cursor.
|
||||
/// </summary>
|
||||
public class Paint : MonoBehaviour
|
||||
{
|
||||
// The minimum amount of cursor movement to be considered a new sample.
|
||||
const float StrokeDeltaThreshold = .1f;
|
||||
const int LeftMouseButton = 0;
|
||||
|
||||
[SerializeField]
|
||||
Mesh m_SampleDot;
|
||||
|
||||
[SerializeField]
|
||||
Material m_SampleMat, m_ControlPointMat;
|
||||
|
||||
// Point reduction epsilon determines how aggressive the point reduction algorithm is when removing redundant
|
||||
// points. Lower values result in more accurate spline representations of the original line, at the cost of
|
||||
// greater number knots.
|
||||
[Range(0f, 1f), SerializeField]
|
||||
float m_PointReductionEpsilon = .15f;
|
||||
|
||||
// Tension affects how "curvy" splines are at knots. 0 is a sharp corner, 1 is maximum curvitude.
|
||||
[Range(0f, 1f), SerializeField]
|
||||
float m_SplineTension = 1 / 4f;
|
||||
|
||||
Label m_Stats;
|
||||
Camera m_Camera;
|
||||
List<float3> m_Stroke = new List<float3>(1024);
|
||||
List<float3> m_Reduced = new List<float3>(512);
|
||||
bool m_Painting;
|
||||
Vector3 m_LastMousePosition;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_Camera = Camera.main;
|
||||
|
||||
m_Stats = PaintUI.root.Q<Label>("Stats");
|
||||
m_Stats.text = "";
|
||||
|
||||
var epsilonSlider = PaintUI.root.Q<Slider>("PointReductionEpsilonSlider");
|
||||
epsilonSlider.RegisterValueChangedCallback(PointReductionEpsilonChanged);
|
||||
epsilonSlider.value = m_PointReductionEpsilon;
|
||||
|
||||
var tensionSlider = PaintUI.root.Q<Slider>("SplineTensionSlider");
|
||||
tensionSlider.RegisterValueChangedCallback(SplineTensionChanged);
|
||||
tensionSlider.value = m_SplineTension;
|
||||
}
|
||||
|
||||
void SplineTensionChanged(ChangeEvent<float> evt)
|
||||
{
|
||||
m_SplineTension = evt.newValue;
|
||||
RebuildSpline();
|
||||
}
|
||||
|
||||
void PointReductionEpsilonChanged(ChangeEvent<float> evt)
|
||||
{
|
||||
m_PointReductionEpsilon = evt.newValue;
|
||||
RebuildSpline();
|
||||
}
|
||||
|
||||
void RebuildSpline()
|
||||
{
|
||||
// Before setting spline knots, reduce the number of sample points.
|
||||
SplineUtility.ReducePoints(m_Stroke, m_Reduced, m_PointReductionEpsilon);
|
||||
|
||||
var spline = GetComponent<SplineContainer>().Spline;
|
||||
|
||||
// Assign the reduced sample positions to the Spline knots collection. Here we are constructing new
|
||||
// BezierKnots from a single position, disregarding tangent and rotation. The tangent and rotation will be
|
||||
// calculated automatically in the next step wherein the tangent mode is set to "Auto Smooth."
|
||||
spline.Knots = m_Reduced.Select(x => new BezierKnot(x));
|
||||
|
||||
var all = new SplineRange(0, spline.Count);
|
||||
|
||||
// Sets the tangent mode for all knots in the spline to "Auto Smooth."
|
||||
spline.SetTangentMode(all, TangentMode.AutoSmooth);
|
||||
|
||||
// Sets the tension parameter for all knots. Note that the "Tension" parameter is only applicable to
|
||||
// "Auto Smooth" mode knots.
|
||||
spline.SetAutoSmoothTension(all, m_SplineTension);
|
||||
|
||||
m_Stats.text = $"Input Sample Count: {m_Stroke.Count}\nSpline Knot Count: {m_Reduced.Count}";
|
||||
}
|
||||
|
||||
void AddSample(Vector2 p)
|
||||
{
|
||||
Vector3 wp = m_LastMousePosition = p;
|
||||
wp.z = 10f;
|
||||
m_Stroke.Add(m_Camera.ScreenToWorldPoint(wp));
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (!PaintUI.PointerOverUI && Input.GetMouseButtonDown(LeftMouseButton))
|
||||
{
|
||||
m_Painting = true;
|
||||
m_Stroke.Clear();
|
||||
AddSample(Input.mousePosition);
|
||||
}
|
||||
|
||||
if (Input.GetMouseButtonUp(LeftMouseButton))
|
||||
{
|
||||
m_Painting = false;
|
||||
RebuildSpline();
|
||||
}
|
||||
|
||||
if (m_Painting && Vector2.Distance(Input.mousePosition, m_LastMousePosition) > StrokeDeltaThreshold)
|
||||
AddSample(Input.mousePosition);
|
||||
|
||||
foreach (var sample in m_Stroke)
|
||||
Graphics.DrawMesh(m_SampleDot, Matrix4x4.TRS(sample, Quaternion.identity, new Vector3(.2f, .2f, .2f)),
|
||||
m_SampleMat, 0);
|
||||
|
||||
foreach (var point in m_Reduced)
|
||||
Graphics.DrawMesh(m_SampleDot,
|
||||
Matrix4x4.TRS((Vector3)point + new Vector3(0f, 0f, -1f), Quaternion.identity,
|
||||
new Vector3(.3f, .3f, .3f)), m_ControlPointMat, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3afd7ccbd91bf490f947bea286824493
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
/// <summary>
|
||||
/// UI implementation for Paint Splines example.
|
||||
/// </summary>
|
||||
public class PaintUI : MonoBehaviour
|
||||
{
|
||||
static bool s_PointerOverUI;
|
||||
public static bool PointerOverUI => s_PointerOverUI;
|
||||
static UIDocument m_Document;
|
||||
public static VisualElement root { get; private set; }
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_Document = GetComponent<UIDocument>();
|
||||
root = m_Document.rootVisualElement;
|
||||
ConnectVisualElements();
|
||||
}
|
||||
|
||||
void ConnectVisualElements()
|
||||
{
|
||||
root.RegisterCallback<PointerEnterEvent>(OnPointerEnter);
|
||||
root.RegisterCallback<PointerLeaveEvent>(OnPointerExit);
|
||||
|
||||
var pointReduceEpsilonSlider = root.Q<Slider>("PointReductionEpsilonSlider");
|
||||
var pointReduceEpsilonLabel = root.Q<Label>("PointReductionEpsilonLabel");
|
||||
pointReduceEpsilonSlider.RegisterValueChangedCallback(evt =>
|
||||
pointReduceEpsilonLabel.text = evt.newValue.ToString());
|
||||
|
||||
var tensionSlider = root.Q<Slider>("SplineTensionSlider");
|
||||
var tensionLabel = root.Q<Label>("SplineTensionLabel");
|
||||
tensionSlider.RegisterValueChangedCallback(evt => tensionLabel.text = evt.newValue.ToString());
|
||||
}
|
||||
|
||||
void OnPointerEnter(PointerEnterEvent evt) => s_PointerOverUI = true;
|
||||
|
||||
void OnPointerExit(PointerLeaveEvent evt) => s_PointerOverUI = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c99dd2a00aa504839914c7de85614714
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public class SelectSplinePath : MonoBehaviour
|
||||
{
|
||||
SplineContainer m_Container;
|
||||
SplinePath<Spline> m_Amalgamate;
|
||||
|
||||
const int k_PreviewCurveResolution = 42;
|
||||
LineRenderer m_LineRenderer;
|
||||
Vector3[] m_CurvePoints;
|
||||
|
||||
void Start()
|
||||
{
|
||||
m_Container = GetComponent<SplineContainer>();
|
||||
m_Amalgamate = new SplinePath<Spline>(m_Container.Splines);
|
||||
m_LineRenderer = GetComponent<LineRenderer>();
|
||||
m_LineRenderer.positionCount = k_PreviewCurveResolution;
|
||||
m_CurvePoints = new Vector3[k_PreviewCurveResolution];
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
using var native = new NativeSpline(m_Amalgamate, m_Container.transform.localToWorldMatrix);
|
||||
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
|
||||
|
||||
float distance = float.PositiveInfinity;
|
||||
var nearest = new BezierCurve();
|
||||
|
||||
for (int i = 0; i < native.Count; ++i)
|
||||
{
|
||||
if(native.GetCurveLength(i) < float.Epsilon)
|
||||
continue;
|
||||
|
||||
var curve = native.GetCurve(i);
|
||||
var dist = CurveUtility.GetNearestPoint(curve, ray, out var p, out _);
|
||||
if (dist < distance)
|
||||
{
|
||||
nearest = curve;
|
||||
distance = dist;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, c = m_CurvePoints.Length; i < c; ++i)
|
||||
m_CurvePoints[i] = CurveUtility.EvaluatePosition(nearest, i / (c - 1f)) + new float3(0f, .1f, 0f);
|
||||
|
||||
m_LineRenderer.SetPositions(m_CurvePoints);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60813d7e4b42848d1b09f81e83bb3370
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
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;
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
m_SplineContainer = FindObjectsByType<SplineContainer>(FindObjectsSortMode.None);
|
||||
#else
|
||||
m_SplineContainer = FindObjectsOfType<SplineContainer>();
|
||||
#endif
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8d0206caa731d581c98dd8df314b4614
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of commonly used functions in the Spline package.
|
||||
/// </summary>
|
||||
class SplineExamples : MonoBehaviour
|
||||
{
|
||||
void Example()
|
||||
{
|
||||
// Splines exist in a scene as properties in a SplineContainer. The relationship of splines to SplineContainer
|
||||
// is similar to the relationship between Mesh and MeshFilter.
|
||||
var container = GetComponent<SplineContainer>();
|
||||
|
||||
// SplineContainer can hold many splines. Access those splines through the Splines property.
|
||||
IReadOnlyList<Spline> splines = container.Splines;
|
||||
|
||||
// Get the position along a spline at a ratio from 0 to 1. 0 is the beginning of the spline and 1 is the end of the spline.
|
||||
// Call the SplineContainer version of EvaluatePosition to get results in world space.
|
||||
float3 worldPosition = container.EvaluatePosition(.5f);
|
||||
|
||||
// Get the position, tangent, and direction of a spline at a location along a spline, and then rotate a GameObject to match the position and rotation of the spline.
|
||||
container.Evaluate(.3f, out var position, out var tangent, out var normal);
|
||||
transform.position = position;
|
||||
transform.rotation = Quaternion.LookRotation(tangent);
|
||||
|
||||
// Knot connections are stored in the KnotLinkConnection type.
|
||||
var links = container.KnotLinkCollection;
|
||||
|
||||
// Knots are referenced by an index to the SplineContainer.Splines array and Knot Index.
|
||||
// This example queries whether any knots are linked to the fourth knot of the first spline.
|
||||
var knotIndex = new SplineKnotIndex(0, 3);
|
||||
if (links.TryGetKnotLinks(knotIndex, out var linked))
|
||||
Debug.Log($"found {linked.Count} connected knots!");
|
||||
|
||||
// SplineSlice represents a partial or complete range of curves from another spline. Slices can iterate either forwards or backwards.
|
||||
// A slice is a value type and does not make copies of the referenced spline. A slice is not resource intensive to create.
|
||||
// Create a new spline from the first curve of another spline.
|
||||
var slice = new SplineSlice<Spline>(splines.First(), new SplineRange(0, 2));
|
||||
|
||||
// Create a SplinePath to evaluate multiple slices of many splines as a single path.
|
||||
var path = new SplinePath(new SplineSlice<Spline>[]
|
||||
{
|
||||
slice,
|
||||
// This range starts at the fourth knot and iterates backwards by three indices.
|
||||
new SplineSlice<Spline>(splines[1], new SplineRange(3, -3))
|
||||
});
|
||||
|
||||
// SplinePath implements ISpline, which you can evaluate with any of the usual SplineUtility methods.
|
||||
var _ = path.EvaluatePosition(.42f);
|
||||
|
||||
// If performance is a concern, use NativeSpline. NativeSpline is a NativeArray backed representation of
|
||||
// any ISpline type. NativeSpline is very efficient to query because all transformations are baked at construction.
|
||||
// Unlike Spline, NativeSpline is not mutable.
|
||||
using var native = new NativeSpline(path, transform.localToWorldMatrix);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8dce1d0ff8c24d538d3c3e4f5a5eab5c
|
||||
timeCreated: 1652903748
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3fcd72b7e265696c4a96291318699084
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Unity.Mathematics;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Splines;
|
||||
|
||||
namespace Unity.Splines.Examples
|
||||
{
|
||||
[Serializable]
|
||||
public struct SplineLineRendererSettings
|
||||
{
|
||||
public float width;
|
||||
public Material material;
|
||||
[Range(16, 512)]
|
||||
public int subdivisions;
|
||||
public Color startColor, endColor;
|
||||
}
|
||||
|
||||
[RequireComponent(typeof(SplineContainer))]
|
||||
public class SplineRenderer : MonoBehaviour
|
||||
{
|
||||
SplineContainer m_SplineContainer;
|
||||
Spline[] m_Splines;
|
||||
bool m_Dirty;
|
||||
Vector3[] m_Points;
|
||||
|
||||
[SerializeField]
|
||||
SplineLineRendererSettings m_LineRendererSettings = new SplineLineRendererSettings() {
|
||||
width = .5f,
|
||||
subdivisions = 64
|
||||
};
|
||||
|
||||
LineRenderer[] m_Lines;
|
||||
|
||||
void Awake()
|
||||
{
|
||||
m_SplineContainer = GetComponent<SplineContainer>();
|
||||
m_Splines = m_SplineContainer.Splines.ToArray();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Spline.Changed += OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modificationType)
|
||||
{
|
||||
for (int i = 0, c = m_Splines.Length; !m_Dirty && i < c; ++i)
|
||||
if (m_Splines[i] == spline)
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
if (m_Lines?.Length != m_Splines.Length)
|
||||
{
|
||||
if (m_Lines != null)
|
||||
foreach (var line in m_Lines) DestroyImmediate(line.gameObject);
|
||||
|
||||
m_Lines = new LineRenderer[m_Splines.Length];
|
||||
|
||||
for (int i = 0, c = m_Splines.Length; i < c; ++i)
|
||||
{
|
||||
m_Lines[i] = new GameObject().AddComponent<LineRenderer>();
|
||||
m_Lines[i].gameObject.name = $"SplineRenderer {i}";
|
||||
m_Lines[i].transform.SetParent(transform, true);
|
||||
}
|
||||
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
// It's nice to be able to see resolution changes at runtime
|
||||
if (m_Points?.Length != m_LineRendererSettings.subdivisions)
|
||||
{
|
||||
m_Dirty = true;
|
||||
m_Points = new Vector3[m_LineRendererSettings.subdivisions];
|
||||
foreach (var line in m_Lines)
|
||||
line.positionCount = m_LineRendererSettings.subdivisions;
|
||||
}
|
||||
|
||||
if (!m_Dirty)
|
||||
return;
|
||||
|
||||
m_Dirty = false;
|
||||
var trs = m_SplineContainer.transform.localToWorldMatrix;
|
||||
|
||||
for (int s = 0, c = m_Splines.Length; s < c; ++s)
|
||||
{
|
||||
if (m_Splines[s].Count < 1)
|
||||
continue;
|
||||
|
||||
for (int i = 0; i < m_LineRendererSettings.subdivisions; i++)
|
||||
m_Points[i] = math.transform(trs, m_Splines[s].EvaluatePosition(i / (m_LineRendererSettings.subdivisions - 1f)));
|
||||
|
||||
m_Lines[s].widthCurve = new AnimationCurve(new Keyframe(0f, m_LineRendererSettings.width));
|
||||
m_Lines[s].startColor = m_LineRendererSettings.startColor;
|
||||
m_Lines[s].endColor = m_LineRendererSettings.endColor;
|
||||
m_Lines[s].material = m_LineRendererSettings.material;
|
||||
m_Lines[s].useWorldSpace = true;
|
||||
m_Lines[s].SetPositions(m_Points);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d9dd4cf612254c08bf888d5bca6bb2d5
|
||||
timeCreated: 1628867781
|
||||
@@ -0,0 +1,104 @@
|
||||
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);
|
||||
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Spline.Changed += OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Spline.Changed -= OnSplineChanged;
|
||||
}
|
||||
|
||||
void OnSplineChanged(Spline spline, int knotIndex, SplineModification modificationType)
|
||||
{
|
||||
if (m_Spline == spline)
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcea5d0b9b5209031bf47c34bf1eb238
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f08613db0663446b876a5eae626aa45
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user