Files
2025-06-09 23:23:13 +08:00

1079 lines
41 KiB
C#

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Splines;
using Unity.Mathematics;
using Unity.Jobs;
using Unity.Collections;
using UnityEngine.Profiling;
namespace JBooth.MicroVerseCore
{
[ExecuteAlways]
[SelectionBase]
public class Road : MonoBehaviour
{
public SplineContainer splineContainer;
[Tooltip("When false, any roll on the spline is automatically prevented")]
public bool allowRoll = false;
[Tooltip("Allows you to disable/enable terrain modification on a road piece")]
public bool modifiesTerrain = true;
[HideInInspector] public Intersection.ConnectionPoint beginConnector;
[HideInInspector] public Intersection.ConnectionPoint endConnector;
[System.Serializable]
public class OverlayEntry
{
public string label;
public GameObject prefab;
public bool none;
}
[System.Serializable]
public class SplineChoiceData
{
public GameObject roadPrefab;
public List<OverlayEntry> overlayEntries = new List<OverlayEntry>();
public OverlayEntry FindOverlayEntry(GameObject prefab)
{
foreach (var p in overlayEntries)
{
if (p.prefab == prefab)
return p;
}
return null;
}
public OverlayEntry FindOverlayEntry(string label)
{
foreach (var p in overlayEntries)
{
if (p.label == label)
return p;
}
return null;
}
}
public SplineChoiceData defaultChoiceData = new SplineChoiceData();
[System.Serializable]
public class SplineChoices
{
public SplineData<SplineChoiceData> choices = new SplineData<SplineChoiceData>();
}
public List<SplineChoices> splineOverlayChoices = new List<SplineChoices>();
[System.Serializable]
public class SplineShapeData
{
public SplineData<float2> shapeData = new SplineData<float2>();
}
public List<SplineShapeData> splineShapes = new List<SplineShapeData>();
SplineChoiceData FindChoiceData(float normalized_t)
{
SplineChoiceData last = null;
foreach (var k in splineOverlayChoices[0].choices)
{
var kn = k.Index;// spline.ConvertIndexUnit(k.Index, PathIndexUnit.Knot, PathIndexUnit.Normalized);
if (normalized_t >= kn)
{
last = k.Value;
}
else if (normalized_t < kn)
{
return last;
}
}
return last;
}
public enum Orientation
{
X = 0, Z = 2
}
public int seed = -1;
[Tooltip("Config for this road")]
public RoadConfig config;
string instanceName = "Generated Road Object";
public List<GameObject> children = new List<GameObject>();
public List<Mesh> meshes = new List<Mesh>();
static Dictionary<Mesh, MeshCacheData> meshCache = new Dictionary<Mesh, MeshCacheData>();
ObjJobHolder objJobHolder = null;
List<VertexJobHolder> bendJobs = new List<VertexJobHolder>();
NativeSpline nspline;
NativeArray<CacheSplineJob.PosQuat> cachePosQuats;
NativeArray<float3> splineWidthArray;
List<ObjectSpawnJobLinearHolder> spawnLinearJobs = new List<ObjectSpawnJobLinearHolder>();
#if UNITY_EDITOR
// hide flags don't save correctly, so we have to reset them.
public void OnEnable()
{
#if __MICROVERSE_SPLINES__
// don't allow spline paths on roads, this cost me like 2 hours of debugging
SplinePath sp = GetComponent<SplinePath>();
if (sp)
{
DestroyImmediate(sp);
}
#endif
RoadSystem rs = GetComponentInParent<RoadSystem>();
if (rs != null)
{
if (rs.hideGameObjects)
{
for (int i = 0; i < this.transform.childCount; ++i)
{
SetHideFlags(this.transform.GetChild(i).gameObject, rs);
}
}
}
}
#endif
static List<Intersection> itersectionCache = new List<Intersection>(100);
public void UpdateConnections(RoadSystem systemRoot, bool allowDisconnnect, bool autoGrabDistance = true, float grabDistance = 4)
{
if (Application.isPlaying)
return;
if (systemRoot == null)
systemRoot = GetComponentInParent<RoadSystem>();
if (systemRoot == null)
return;
Profiler.BeginSample("Update Connections");
Profiler.BeginSample("Find Intersections");
systemRoot.GetComponentsInChildren<Intersection>(itersectionCache);
Profiler.EndSample();
if (autoGrabDistance && config != null)
grabDistance = config.modelWidth * 0.75f;
// TODO: Rewrite to find closest points, not first qualifying.
foreach (var intersection in itersectionCache)
{
if (splineContainer != null)
{
var spline = splineContainer.Spline;
#if UNITY_EDITOR
bool dirty = false;
#endif
bool needsUpdate = false;
if (spline.Count > 0)
{
var wsp = splineContainer.transform.localToWorldMatrix.MultiplyPoint(spline[0].Position);
foreach (var anchor in intersection.connectionPoints)
{
if (Vector3.Distance(anchor.connector.transform.position, wsp) < grabDistance)
{
if (anchor.container == null)
{
anchor.front = true;
anchor.container = splineContainer;
anchor.road = this;
beginConnector = anchor;
anchor.owner = intersection;
#if UNITY_EDITOR
dirty = true;
#endif
}
needsUpdate = true;
}
else
{
if (allowDisconnnect && anchor.container == splineContainer && anchor.front == true)
{
anchor.container = null;
anchor.owner = null;
beginConnector = null;
anchor.road = null;
#if UNITY_EDITOR
dirty = true;
#endif
}
}
}
}
if (spline.Count > 1)
{
var wsp = splineContainer.transform.localToWorldMatrix.MultiplyPoint(spline[spline.Count - 1].Position);
foreach (var anchor in intersection.connectionPoints)
{
if (Vector3.Distance(anchor.connector.transform.position, wsp) < grabDistance)
{
if (anchor.container == null)
{
anchor.front = false;
anchor.container = splineContainer;
anchor.road = this;
endConnector = anchor;
anchor.owner = intersection;
#if UNITY_EDITOR
dirty = true;
#endif
}
needsUpdate = true;
}
else if (allowDisconnnect && anchor.container == splineContainer && anchor.front == false)
{
anchor.container = null;
anchor.road = null;
endConnector = null;
anchor.road = null;
#if UNITY_EDITOR
dirty = true;
#endif
}
}
}
if (needsUpdate)
{
intersection.UpdateConnections(systemRoot);
}
#if UNITY_EDITOR
if (dirty)
{
UnityEditor.EditorUtility.SetDirty(intersection);
}
#endif
}
}
Profiler.EndSample();
}
#if UNITY_EDITOR
private void OnDestroy()
{
CleanupMeshes();
if (MicroVerse.instance != null && Application.isPlaying == false)
{
var rs = GetComponentInParent<RoadSystem>();
if (rs != null)
{
rs.UpdateSystem(null);
}
}
}
#endif
void CleanupMeshes()
{
Profiler.BeginSample("Cleanup Spline Road");
int meshNulls = 0;
int childNulls = 0;
foreach (var m in meshes)
{
if (m != null)
DestroyImmediate(m);
else
meshNulls++;
}
foreach (var c in children)
{
if (c != null)
DestroyImmediate(c);
else
childNulls++;
}
// We lost references, need to check children
// This happens when a RoadSystem - Road is part of a Prefab
// Pressing Undo clears these references
if ((meshNulls == meshes.Count || childNulls == children.Count) && transform.childCount > 0)
{
for(int i = 0; i < transform.childCount; i++)
{
Transform child = transform.GetChild(i);
// Not our object
if(child.name != instanceName)
{
continue;
}
var allTrans = child.GetComponentsInChildren<Transform>();
foreach (Transform t in allTrans)
{
MeshFilter mf = t.GetComponent<MeshFilter>();
if (mf != null && mf.sharedMesh.name == instanceName)
meshes.Add(mf.sharedMesh);
}
children.Add(child.gameObject);
}
CleanupMeshes();
}
children.Clear();
meshes.Clear();
Profiler.EndSample();
}
public static void SetHideFlags(Object o, RoadSystem rs)
{
if (o == null) return;
if (rs == null)
{
o.hideFlags = HideFlags.None;
}
else if (rs.generationOption == RoadSystem.RoadGenerationOption.GeneratePlaymode || rs.generationOption == RoadSystem.RoadGenerationOption.GenerateRuntime)
{
if (rs.hideGameObjects)
o.hideFlags = HideFlags.HideAndDontSave;
else
o.hideFlags = HideFlags.DontSave;
}
else
{
if (rs.hideGameObjects)
o.hideFlags = HideFlags.HideInHierarchy;
else
o.hideFlags = HideFlags.None;
}
}
struct BendMeshData
{
public GameObject owner;
public MeshFilter mf;
public MeshCollider mc;
public Mesh mesh;
public float start;
public float range;
public float meshLength;
public float scale;
public int orient;
public NativeArray<CacheSplineJob.PosQuat> posQuats;
public JobHandle cacheSplineJob;
public BendRules.CullMode cullMode;
public Vector2 globalScaleBegin;
public Vector2 globalScaleEnd;
}
void BendMesh(BendMeshData bd)
{
if (bd.mesh.isReadable)
{
Profiler.BeginSample("Mesh Cache Management");
NativeArray<Vector3> vertices;
NativeArray<Vector3> normals;
NativeArray<Vector4> tangents;
MeshCacheData cacheData;
if (meshCache.ContainsKey(bd.mesh))
{
cacheData = meshCache[bd.mesh];
vertices = cacheData.vertices;
normals = cacheData.normals;
tangents = cacheData.tangents;
}
else
{
int count = bd.mesh.vertexCount;
vertices = new NativeArray<Vector3>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
vertices.CopyFrom(bd.mesh.vertices);
var meshNormals = bd.mesh.normals;
if (meshNormals != null && meshNormals.Length == count)
{
normals = new NativeArray<Vector3>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
normals.CopyFrom(meshNormals);
}
else
{
normals = new NativeArray<Vector3>(1, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
}
var meshTangent = bd.mesh.tangents;
if (meshTangent != null && meshTangent.Length == count)
{
tangents = new NativeArray<Vector4>(count, Allocator.Persistent, NativeArrayOptions.UninitializedMemory);
tangents.CopyFrom(meshTangent);
}
else
{
tangents = new NativeArray<Vector4>(1, Allocator.Persistent);
}
cacheData = new MeshCacheData() { vertices = vertices, normals = normals, tangents = tangents };
meshCache.Add(bd.mesh, cacheData);
}
Profiler.EndSample();
Profiler.BeginSample("Alloc Native Arrays");
NativeArray<Bounds> bounds = new NativeArray<Bounds>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
NativeArray<Vector4> tangs = new NativeArray<Vector4>(tangents.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
NativeArray<Vector3> norms = new NativeArray<Vector3>(normals.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
NativeArray<Vector3> verts = new NativeArray<Vector3>(vertices.Length, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
Profiler.EndSample();
Profiler.BeginSample("CopyFrom");
verts.CopyFrom(vertices);
norms.CopyFrom(normals);
tangs.CopyFrom(tangents);
Profiler.EndSample();
Profiler.BeginSample("Schedule Bends");
var old = splineContainer.transform.position;
splineContainer.transform.position = Vector3.zero;
BendVertexJob bj = new BendVertexJob()
{
localToWorld = bd.owner.transform.localToWorldMatrix,
worldToLocal = bd.owner.transform.worldToLocalMatrix,
start = bd.start,
range = bd.range,
meshLength = bd.meshLength,
meshScale = bd.scale,
orientation = bd.orient,
positions = verts,
normals = norms,
tangents = tangs,
posQuats = bd.posQuats,
bounds = bounds,
allowRoll = allowRoll,
cullingMode = bd.cullMode,
globalScaleBegin = bd.globalScaleBegin,
globalScaleEnd = bd.globalScaleEnd,
localPos = bd.owner.transform.localPosition
};
splineContainer.transform.position = old;
VertexJobHolder jh = new VertexJobHolder();
bendJobs.Add(jh);
jh.meshCollider = bd.mc;
jh.cacheData = cacheData;
jh.bendJob = bj;
jh.meshFilter = bd.mf;
jh.mesh = bd.mesh;
jh.bendHandle = bj.Schedule(bd.cacheSplineJob);
Profiler.EndSample();
}
else
{
Debug.LogError("Mesh : " + bd.mesh.name + " is not set to read-write, cannot bend");
}
}
void Bend(GameObject prefab, float start, float range,
float meshLength, float scale, int orient, float curLength, float totalLength,
ObjJobHolder objJobHolder,
NativeArray<CacheSplineJob.PosQuat> posQuats,
JobHandle cacheSplineJob, ref Unity.Mathematics.Random random, RoadSystem roadSystem)
{
LinearObjectRules[] lors = prefab.GetComponentsInChildren<LinearObjectRules>();
if (lors != null && lors.Length > 0)
{
if (start <= 0)
{
foreach (var lor in lors)
{
Spline spline = splineContainer[0];
Vector3 offset = lor.transform.position;
offset[orient] = 0;
ObjSpawnJobLinear job = new ObjSpawnJobLinear()
{
beginOffset = lor.beginOffset,
linearDistance = lor.linearDistance,
offset = offset,
spline = nspline,
positions = new NativeList<float3>(32, Allocator.TempJob),
quaternions = new NativeList<quaternion>(32, Allocator.TempJob),
};
var handle = job.Schedule();
ObjectSpawnJobLinearHolder holder = new ObjectSpawnJobLinearHolder()
{
job = job,
handle = handle,
prefab = lor.gameObject,
};
spawnLinearJobs.Add(holder);
}
}
// don't do any further processing as this is a null op on any other start value
return;
}
Profiler.BeginSample("Set Physics");
MeshCollider[] mcs = prefab.GetComponentsInChildren<MeshCollider>();
foreach (var mc in mcs)
{
mc.enabled = false; // disable physics until save
}
Profiler.EndSample();
Profiler.BeginSample("Instantiate prefabs");
#if false // this breaks multi material instantiation for some reason..
GameObject prefabInstance = (GameObject)UnityEditor.PrefabUtility.InstantiatePrefab(prefab);
prefabInstance.transform.localPosition = Vector3.zero;
prefabInstance.transform.localRotation = Quaternion.identity;
#else
GameObject prefabInstance = Instantiate(prefab, Vector3.zero, Quaternion.identity);
#endif
prefabInstance.tag = gameObject.tag;
prefabInstance.layer = gameObject.layer;
SetHideFlags(prefabInstance, roadSystem);
// Can't use tags ^
// Using this to mark objects spawned by the Road
// In case of losing references to child objects, this can identify objects spawned by the Road
// Need to mark them, cause the User might have objects under their Road object
prefabInstance.name = instanceName;
children.Add(prefabInstance);
Profiler.EndSample();
Profiler.BeginSample("Gather Sub-Objects");
var allTrans = prefabInstance.transform.GetComponentsInChildren<Transform>();
Profiler.EndSample();
foreach (var trans in allTrans)
{
GameObject go = trans.gameObject;
MeshFilter mf = go.GetComponent<MeshFilter>();
MeshCollider mc = go.GetComponent<MeshCollider>();
BendRules rules = go.GetComponent<BendRules>();
if (rules == null)
rules = go.GetComponentInParent<BendRules>();
if (rules == null || rules.mode == BendRules.Mode.Bend)
{
Profiler.BeginSample("Create Bend Job");
if (!BendRules.ShouldSpawn(rules, curLength, meshLength, totalLength, random))
{
DestroyImmediate(go);
Profiler.EndSample();
continue;
}
if (rules != null && rules.spawnRules.cullingMode == BendRules.CullMode.Cull && curLength < meshLength * rules.spawnRules.requiredLeft)
{
DestroyImmediate(go);
Profiler.EndSample();
continue;
}
var cullMode = BendRules.CullMode.Clamp;
if (rules != null)
{
cullMode = rules.spawnRules.cullingMode;
}
float2 gss = 1;
float2 gse = 1;
if (beginConnector != null && beginConnector.owner != null)
{
gss = new float2(beginConnector.owner.transform.lossyScale.x, beginConnector.owner.transform.lossyScale.y);
}
if (endConnector != null && endConnector.owner != null)
{
gse = new float2(endConnector.owner.transform.lossyScale.x, endConnector.owner.transform.lossyScale.y);
}
BendMeshData bmd = new BendMeshData()
{
owner = go,
mf = mf,
mc = mc,
start = start,
range = range,
meshLength = meshLength,
scale = scale,
orient = orient,
posQuats = posQuats,
cacheSplineJob = cacheSplineJob,
cullMode = cullMode,
globalScaleBegin = gss,
globalScaleEnd = gse
};
if (mf != null && mc != null && mf.sharedMesh != null && mc.sharedMesh != null)
{
// share one mesh
if (mf.sharedMesh == mc.sharedMesh)
{
bmd.mesh = mf.sharedMesh;
BendMesh(bmd);
}
else
{
bmd.mesh = mf.sharedMesh;
bmd.mc = null;
BendMesh(bmd);
bmd.mc = mc;
bmd.mf = null;
bmd.mesh = mc.sharedMesh;
BendMesh(bmd);
}
}
else if (mf != null && mf.sharedMesh != null) // just a mesh filter
{
bmd.mc = null;
bmd.mesh = mf.sharedMesh;
BendMesh(bmd);
}
else if (mc != null && mc.sharedMesh != null) // just a collider
{
bmd.mf = null;
bmd.mesh = mc.sharedMesh;
BendMesh(bmd);
}
Profiler.EndSample();
}
else if (rules.mode != BendRules.Mode.None)
{
if (trans.parent != prefabInstance.transform)
continue;
Profiler.BeginSample("Append Object To Job");
if (!BendRules.ShouldSpawn(rules, curLength, meshLength, totalLength, random))
{
go.SetActive(false);
Profiler.EndSample();
continue;
}
/// this should likely be done in world space instead? Wouldn't have to stop one level deep.
var entry = new ObjectSpawnJob.ObjEntry()
{
position = go.transform.localPosition,
start = start,
range = range,
meshLength = meshLength,
bendRule = rules.mode,
scaleUniform = rules.placeRules.scaleUniform,
positionVariance = rules.placeRules.positionVariance,
rotationVariance = rules.placeRules.rotationVariance,
scaleVariance = rules.placeRules.scaleVariant,
cullingMode = rules.spawnRules.cullingMode,
};
objJobHolder.entries.Add(entry);
objJobHolder.transforms.Add(go.transform);
Profiler.EndSample();
}
}
prefabInstance.transform.SetParent(splineContainer.transform);
prefabInstance.transform.localPosition = Vector3.zero;
prefabInstance.transform.localRotation = Quaternion.identity;
}
public static void ClearCache()
{
foreach (var mc in meshCache.Values)
{
mc.vertices.Dispose();
mc.normals.Dispose();
mc.tangents.Dispose();
}
meshCache.Clear();
}
public void LaunchJobs(RoadSystem rs)
{
bendJobs.Clear();
var spline = splineContainer.Spline;
if (spline == null || spline.Count < 1)
{
return;
}
Profiler.BeginSample("Generate Spline Road");
Profiler.BeginSample("Pre Launch Jobs");
//UpdateConnections(rs, false);
CleanupMeshes();
if (seed < 0)
{
seed = UnityEngine.Random.Range(1, 1024);
}
var random = new Unity.Mathematics.Random((uint)seed);
objJobHolder = new ObjJobHolder();
int orient = (int)config.orientation;
float4x4 xfm = splineContainer.transform.localToWorldMatrix;
float length = spline.CalculateLength(xfm);
float totalLength = length;
nspline = new NativeSpline(spline, Allocator.TempJob);
float start = 0;
Profiler.EndSample();
// We cache the spline at a 1 meter resolution. Takes 0.25 ms to update 1200 points.
// Values are interpolated between that resolution.
Profiler.BeginSample("Cache Spline Job");
int sampleCount = (int)(totalLength) + 1;
cachePosQuats = new NativeArray<CacheSplineJob.PosQuat>((int)sampleCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
if (rs != null && rs.systemConfig != null && rs.systemConfig.allowShaping && splineShapes.Count > 0 && splineShapes[0].shapeData.Count > 0)
{
var shapes = splineShapes[0].shapeData;
splineWidthArray = new NativeArray<float3>(shapes.Count, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
for (int i = 0; i < shapes.Count; ++i)
{
var v = shapes[i].Value;
splineWidthArray[i] = new float3(shapes[i].Index, v.x, v.y);
}
}
else
{
splineWidthArray = new NativeArray<float3>(1, Allocator.TempJob, NativeArrayOptions.UninitializedMemory);
splineWidthArray[0] = 0;
}
CacheSplineJob cacheJob = new CacheSplineJob()
{
spline = nspline,
shapeData = splineWidthArray,
data = cachePosQuats,
sampleCount = (int)sampleCount
};
var cacheSplineHandle = cacheJob.Schedule((int)sampleCount, 64);
Profiler.EndSample();
Profiler.BeginSample("Generate Jobs");
var entry = config.entries[0];
float scaledEntrySize = entry.size;
float scale = 1;
if (config.stretchToFit)
{
// we add a tiny bit of length, otherwise we get sliver pieces from floating point rounding errors
float count = (totalLength + 0.001f) / entry.size + config.stretchToFitBoost;
scale = (count / Mathf.Round(count));
scaledEntrySize *= scale;
}
while (length > 0)
{
GameObject prefabToSpawn = entry.prefab;
SplineChoiceData choice = null;
if (splineOverlayChoices.Count > 0)
{
choice = FindChoiceData(start / totalLength);
}
if (choice == null)
choice = defaultChoiceData;
if (choice != null && choice.roadPrefab != null)
prefabToSpawn = choice.roadPrefab;
float start01 = start / totalLength;
float range = scaledEntrySize / totalLength;
// do the main road, essentially
Bend(prefabToSpawn, start01, range, scaledEntrySize, scale,
orient, length, totalLength, objJobHolder,
cachePosQuats, cacheSplineHandle, ref random, rs);
var overlays = config.GetAllOverlays(entry);
if (choice == null && overlays != null && overlays.Count > 0)
{
for (int i = 0; i < overlays.Count; ++i)
{
var overlay = overlays[i];
if (overlay != null && overlay.prefabs.Length > 0 && overlay.spawnFirstAsDefault)
{
if (random.NextFloat(1.0f) < overlay.overlayChance)
{
Bend(overlay.prefabs[0], start01, range, scaledEntrySize, scale,
orient, length, totalLength, objJobHolder,
cachePosQuats, cacheSplineHandle, ref random, rs);
}
}
}
}
else if (choice != null)
{
for (int i = 0; i < choice.overlayEntries.Count; ++i)
{
var e = choice.overlayEntries[i];
if (e.prefab == null && e.none == false) // spawn default
{
var overlay = config.FindOverlay(entry, e.label);
if (overlay != null && overlay.prefabs.Length > 0 && overlay.spawnFirstAsDefault)
{
if (random.NextFloat(1.0f) < overlay.overlayChance)
{
Bend(overlay.prefabs[0], start01, range, scaledEntrySize, scale,
orient, length, totalLength, objJobHolder,
cachePosQuats, cacheSplineHandle, ref random, rs);
}
}
}
else if (e.prefab != null)
{
Bend(e.prefab, start01, range, scaledEntrySize, scale,
orient, length, totalLength, objJobHolder,
cachePosQuats, cacheSplineHandle, ref random, rs);
}
}
}
float size = scaledEntrySize;
if (size <= 0) size = 1;
length -= size;
start += size;
}
if (objJobHolder.entries.Count > 0)
{
float2 gss = 1;
float2 gse = 1;
if (beginConnector != null && beginConnector.owner != null)
{
gss = new float2(beginConnector.owner.transform.lossyScale.x, beginConnector.owner.transform.lossyScale.y);
}
if (endConnector != null && endConnector.owner != null)
{
gse = new float2(endConnector.owner.transform.lossyScale.x, endConnector.owner.transform.lossyScale.y);
}
objJobHolder.objBendJob = new ObjectSpawnJob()
{
orientation = orient,
allowRoll = allowRoll,
entries = new NativeArray<ObjectSpawnJob.ObjEntry>(objJobHolder.entries.ToArray(), Allocator.TempJob),
posQuats = cachePosQuats,
meshLength = length,
globalScaleBegin = gss,
globalScaleEnd = gse,
};
objJobHolder.handle = objJobHolder.objBendJob.Schedule(cacheSplineHandle);
}
Profiler.EndSample();
Profiler.EndSample();
}
public void CancelJobs()
{
if (bendJobs.Count == 0)
return;
Profiler.BeginSample("Cancel Bend Jobs");
for (int i = 0; i < bendJobs.Count; ++i)
{
var job = bendJobs[i];
job.bendHandle.Complete();
job.bendJob.positions.Dispose();
job.bendJob.normals.Dispose();
job.bendJob.tangents.Dispose();
job.bendJob.bounds.Dispose();
}
for (int i = 0; i < spawnLinearJobs.Count; ++i)
{
var job = spawnLinearJobs[i];
job.handle.Complete();
job.job.positions.Dispose();
job.job.quaternions.Dispose();
}
spawnLinearJobs.Clear();
bendJobs.Clear();
nspline.Dispose();
cachePosQuats.Dispose();
splineWidthArray.Dispose();
objJobHolder.objBendJob.entries.Dispose();
Profiler.EndSample();
}
public void ProcessJobs(RoadSystem rs)
{
Profiler.BeginSample("Process Jobs");
if (objJobHolder != null && objJobHolder.entries.Count > 0)
{
Profiler.BeginSample("Object Job");
objJobHolder.handle.Complete();
for (int i = 0; i < objJobHolder.entries.Count; ++i)
{
var entry = objJobHolder.objBendJob.entries[i];
var trans = objJobHolder.transforms[i];
trans.localPosition = entry.position;
trans.localScale *= entry.scale + 1.0f;
var quat = entry.quaternion;
if (quat.value.Equals(float4.zero))
{
trans.gameObject.SetActive(false);
}
else
{
trans.transform.localRotation *= quat;
}
}
objJobHolder.objBendJob.entries.Dispose();
Profiler.EndSample();
}
Profiler.BeginSample("Processing Linear Spawn Jobs");
for (int i = 0; i < spawnLinearJobs.Count; ++i)
{
Profiler.BeginSample("Wait for jobs");
var job = spawnLinearJobs[i];
job.handle.Complete();
Profiler.EndSample();
Profiler.BeginSample("Instance Linear Objects");
for (int x = 0; x < job.job.positions.Length; ++x)
{
var quat = job.job.quaternions[x];
if (quat.value.Equals(float4.zero))
{
continue;
}
var pos = job.job.positions[x];
var inst = Object.Instantiate(job.prefab, this.transform);
children.Add(inst);
inst.name = instanceName;
inst.transform.localPosition = pos;
inst.transform.localRotation *= quat;
SetHideFlags(inst, rs);
}
Profiler.EndSample();
job.job.positions.Dispose();
job.job.quaternions.Dispose();
}
spawnLinearJobs.Clear();
Profiler.EndSample();
Profiler.BeginSample("Mesh Bend Jobs");
for (int i = 0; i < bendJobs.Count; ++i)
{
Profiler.BeginSample("Wait for jobs");
var job = bendJobs[i];
job.bendHandle.Complete();
Profiler.EndSample();
Profiler.BeginSample("Instance Mesh");
// about 10x the speed of calling Instantiate, and less memory alloc
Mesh m = new Mesh();
var meshData = Mesh.AcquireReadOnlyMeshData(job.mesh);
Mesh.ApplyAndDisposeWritableMeshData(meshData, m);
SetHideFlags(m, rs);
Profiler.EndSample();
Profiler.BeginSample("Set Mesh Data");
m.name = instanceName;
meshes.Add(m);
m.SetVertices(job.bendJob.positions);
if (job.bendJob.positions.Length == job.bendJob.normals.Length)
{
m.SetNormals(job.bendJob.normals);
}
if (job.bendJob.positions.Length == job.bendJob.tangents.Length)
{
m.SetTangents(job.bendJob.tangents);
}
m.bounds = job.bendJob.bounds[0];
if (job.meshFilter != null)
job.meshFilter.sharedMesh = m;
if (job.meshCollider != null)
job.meshCollider.sharedMesh = m;
job.bendJob.positions.Dispose();
job.bendJob.normals.Dispose();
job.bendJob.tangents.Dispose();
job.bendJob.bounds.Dispose();
Profiler.EndSample();
}
bendJobs.Clear();
Profiler.EndSample();
nspline.Dispose();
cachePosQuats.Dispose();
splineWidthArray.Dispose();
Profiler.EndSample();
Profiler.BeginSample("Material Override");
if (rs != null && rs.templateMaterial != null)
{
RoadMaterialOverride[] overs = GetComponentsInChildren<RoadMaterialOverride>();
if (overs != null)
{
foreach (var o in overs)
{
o.Override(rs.templateMaterial);
}
}
}
Profiler.EndSample();
}
public void Generate(RoadSystem rs = null, bool updateMS = true)
{
if (rs == null)
rs = GetComponentInParent<RoadSystem>();
#if __MICROVERSE__ && __MICROVERSE_SPLINES__
if (GetComponentInParent<MicroVerse>() != null && updateMS && !Application.isPlaying)
{
if (rs != null)
{
SplinePath sp = rs.GetComponent<SplinePath>();
if (sp != null && MicroVerse.instance != null)
{
Bounds b = SplineUtility.GetBounds(splineContainer.Spline, splineContainer.transform.localToWorldMatrix);
MicroVerse.instance.AddRoadJob(this, rs, b);
MicroVerse.instance.Invalidate(sp.GetBounds());
return;
}
}
}
#endif
LaunchJobs(rs);
ProcessJobs(rs);
}
}
}