// void Spawn(float deltaTime, ref DirectedGraph <SplineGraphPayload, SplineGraphPayloadSerializable> splineGraph) { if (count >= requestedCount) { return; } for (; count < requestedCount; ++count) { float t = UnityEngine.Random.value; Int16 edgeIndex = (Int16)Mathf.FloorToInt((splineGraph.edgePoolChildren.data.Length - 1) * UnityEngine.Random.value + 0.5f); int isComplete = 0; int isReverse = isTwoWayPathEnabled ? ((UnityEngine.Random.value >= 0.5f) ? 1 : 0) : 0; followStates[count] = new SplineMath.SplineGraphFollowState(t, edgeIndex, isComplete, isReverse); float velocityRandom = UnityEngine.Random.value; randoms[count] = new Unity.Mathematics.Random((uint)count + 1); velocities[count] = Mathf.Lerp(velocityMin, velocityMax, velocityRandom); scales[count] = Mathf.Lerp(1.0f, 1.0f, UnityEngine.Random.value); float leashPolarRadiusNormalized = math.lerp(leashNormalizedMin, leashNormalizedMax, math.pow(1.0f - velocityRandom, 2.0f)); float leashPolarThetaMin = isTwoWayPathEnabled ? (-0.5f * math.PI) : 0.0f; float leashPolarThetaMax = isTwoWayPathEnabled ? (0.5f * math.PI) : math.PI * 2.0f; float leashPolarTheta = math.lerp(leashPolarThetaMin, leashPolarThetaMax, UnityEngine.Random.value); float2 leashCartesianNormalized = new float2( math.cos(leashPolarTheta), math.sin(leashPolarTheta) ) * leashPolarRadiusNormalized; leashes[count] = leashCartesianNormalized; // Seed position and rotation with their initial values, so that dampening does not lerp between the previous garbage position of a newly spawned vehicle. // If this probes to make Spawn() significantly more expensive, we can store a flag that says whether or not we should apply dampening instead. { SplineMath.Spline spline = (isReverse == 0) ? splineGraph.payload.edgeParentToChildSplines.data[edgeIndex] : splineGraph.payload.edgeChildToParentSplines.data[edgeIndex]; Int16 vertexIndexChild = splineGraph.edgePoolChildren.data[edgeIndex].vertexIndex; Int16 vertexIndexParent = splineGraph.edgePoolParents.data[edgeIndex].vertexIndex; if (isReverse == 1) { Int16 vertexIndexTemp = vertexIndexChild; vertexIndexChild = vertexIndexParent; vertexIndexParent = vertexIndexTemp; } quaternion rotationParent = splineGraph.payload.rotations.data[vertexIndexParent]; quaternion rotationChild = splineGraph.payload.rotations.data[vertexIndexChild]; float3 positionOnSpline = SplineMath.EvaluatePositionFromT(spline, t); positions[count] = positionOnSpline; rotations[count] = SplineMath.EvaluateRotationWithRollFromT(spline, rotationParent, rotationChild, t); } followPool.EnableInstanceNext(); } }
// TODO: Debug only: private void OnSceneGUI() { // If we are currently tumbling the camera, do not attempt to do anything else. if (Event.current.alt) { return; } serializedObject.Update(); var sgm = target as SplineGraphManager; sgm.Verify(); if (!sgm.isEditingEnabled) { return; } // return; // TODO: Remove? if (Tools.current == Tool.Move) { sgm.debugPosition = Handles.PositionHandle(sgm.debugPosition, quaternion.identity); for (int i = 0, iCount = sgm.debugFollowStates.Count; i < iCount; ++i) { SplineMath.SplineGraphFollowState state = sgm.debugFollowStates[i]; int isReverse = state.DecodeIsReverse(); Int16 edgeIndex = state.DecodeEdgeIndex(); float t = state.t; SplineMath.Spline spline = (state.DecodeIsReverse() == 0) ? sgm.splineGraph.payload.edgeParentToChildSplines.data[edgeIndex] : sgm.splineGraph.payload.edgeChildToParentSplines.data[edgeIndex]; float3 position = SplineMath.EvaluatePositionFromT(spline, t); quaternion rotation = SplineMath.EvaluateRotationFromT(spline, t); Handles.PositionHandle(position, rotation); } } if (sgm.isAutoUpdateEnabled) { sgm.BuildGraphFromInstances(); } }
void UpdateMeshFromSplineGraph() { Mesh mesh = meshFilter.sharedMesh; if (mesh == null) { mesh = new Mesh(); } if (radialEdgeCount <= 0) { // TODO: Cleanup case when radialEdgeCount == 0 due to user still editing into inspector field. // This should be handled with a delayed int field. return; } var splineGraph = (splineGraphComponent != null) ? splineGraphComponent.GetSplineGraph() : splineGraphManager.GetSplineGraph(); // First, go through and compute the number of vertex ring subdivisions required to represent the spline graph based on curvature. Int16 vertexIsValidCount = splineGraph.ComputeVertexIsValidCount(); Int16 edgeIsValidCount = splineGraph.ComputeEdgeIsValidCount(); int[] edgeSubdivisionIndex = new int[splineGraph.edgePoolChildren.count]; int[] edgeSubdivisionCount = new int[splineGraph.edgePoolChildren.count]; int meshSubdivisionCountTotal = 0; int meshRingCountTotal = 0; for (Int16 edgeIndex = 0; edgeIndex < splineGraph.edgePoolChildren.count; ++edgeIndex) { DirectedEdge edge = splineGraph.edgePoolChildren.data[edgeIndex]; if (edge.IsValid() == 0) { continue; } float splineLength = splineGraph.payload.edgeLengths.data[edgeIndex]; int subdivisionCount = math.max(1, (int)math.floor(subdivisionsPerMeter * splineLength + 0.5f)); int ringCount = subdivisionCount + 1; edgeSubdivisionIndex[edgeIndex] = meshSubdivisionCountTotal; edgeSubdivisionCount[edgeIndex] = subdivisionCount; meshSubdivisionCountTotal += subdivisionCount; meshRingCountTotal += ringCount; } // TODO: Calculate counts. int meshVertexCount = meshRingCountTotal * radialEdgeCount; Vector3[] vertices = new Vector3[meshVertexCount]; Vector2[] uvs = new Vector2[meshVertexCount]; Vector3[] normals = new Vector3[meshVertexCount]; Color[] colors = new Color[meshVertexCount]; int[] triangles = new int[meshVertexCount * 6]; int meshVertexIndex = 0; int meshTriangleIndex = 0; for (Int16 edgeIndex = 0; edgeIndex < splineGraph.edgePoolChildren.count; ++edgeIndex) { DirectedEdge edge = splineGraph.edgePoolChildren.data[edgeIndex]; if (edge.IsValid() == 0) { continue; } Int16 vertexIndexChild = splineGraph.edgePoolChildren.data[edgeIndex].vertexIndex; Int16 vertexIndexParent = splineGraph.edgePoolParents.data[edgeIndex].vertexIndex; SplineMath.Spline spline = splineGraph.payload.edgeParentToChildSplines.data[edgeIndex]; float splineLength = splineGraph.payload.edgeLengths.data[edgeIndex]; quaternion rotationParent = splineGraph.payload.rotations.data[vertexIndexParent]; quaternion rotationChild = splineGraph.payload.rotations.data[vertexIndexChild]; SplineMath.Spline splineLeash = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndex]; // Find neighboring edge (if it exists) for use in CSG style operation against current one to handle intersections in branching region. DirectedVertex vertexParent = splineGraph.vertices.data[vertexIndexParent]; Debug.Assert(vertexParent.IsValid() == 1); DirectedVertex vertexChild = splineGraph.vertices.data[vertexIndexChild]; Debug.Assert(vertexChild.IsValid() == 1); int edgeSubdivisionCurrentCount = edgeSubdivisionCount[edgeIndex]; for (int s = 0; s <= edgeSubdivisionCurrentCount; ++s) { float t = (float)s / (float)edgeSubdivisionCurrentCount; // Debug.Log("s = " + s + ", sCount = " + edgeSubdivisionCurrentCount + ", t = " + t); float vNormalized = t; if (s > 0) { // Compute the relative distance we have traveled between our current edge ring and previous. SplineMath.ComputeSplitAtT(out SplineMath.Spline s00, out SplineMath.Spline s01, spline, t); vNormalized = SplineMath.ComputeLengthEstimate(s00, 1e-5f) / splineLength; } float3 positionOnSpline = SplineMath.EvaluatePositionFromT(spline, t); quaternion rotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(spline, rotationParent, rotationChild, t); // float2 leashMaxOS = math.lerp(leashParent, leashChild, t); float2 leashMaxOS = SplineMath.EvaluatePositionFromT(splineLeash, t).xy; // Generate radial ring of triangles if (s < edgeSubdivisionCurrentCount) { for (int v = 0, vLen = radialEdgeCount; v < vLen; ++v) { triangles[meshTriangleIndex + v * 6 + 0] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (0 * radialEdgeCount); triangles[meshTriangleIndex + v * 6 + 1] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (1 * radialEdgeCount); triangles[meshTriangleIndex + v * 6 + 2] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (1 * radialEdgeCount); triangles[meshTriangleIndex + v * 6 + 3] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (1 * radialEdgeCount); triangles[meshTriangleIndex + v * 6 + 4] = meshVertexIndex + ((v + 1) % radialEdgeCount) + (0 * radialEdgeCount); triangles[meshTriangleIndex + v * 6 + 5] = meshVertexIndex + ((v + 0) % radialEdgeCount) + (0 * radialEdgeCount); } meshTriangleIndex += radialEdgeCount * 6; } // Generate radial ring of vertices. // bool ringIntersectsSibling = false; for (int v = 0; v < radialEdgeCount; ++v) { float thetaNormalized = (float)v / (float)radialEdgeCount; float theta = thetaNormalized * 2.0f * math.PI; float2 vertexOffsetOS = new float2( math.cos(theta), math.sin(theta) ) * leashMaxOS;// * radius; float3 vertexOffsetWS = math.mul(rotationOnSpline, new float3(vertexOffsetOS, 0.0f)); float3 vertexPositionWS = positionOnSpline + vertexOffsetWS; float vertexIntersectionSignedDistanceMin = float.MaxValue; { for (Int16 edgeIndexSibling = vertexParent.childHead; edgeIndexSibling != -1; edgeIndexSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling].next) { DirectedEdge edgeSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling]; Debug.Assert(edgeSibling.IsValid() == 1); if (edgeIndexSibling == edgeIndex) { // Ignore ourselves. continue; } // Found our sibling edge. Only use the first one, as we only currently support CSG against a single branch. Int16 siblingVertexIndexChild = edgeSibling.vertexIndex; Debug.Assert(siblingVertexIndexChild != -1); Int16 siblingVertexIndexParent = splineGraph.edgePoolParents.data[edgeIndexSibling].vertexIndex; Debug.Assert(siblingVertexIndexParent != -1); SplineMath.Spline splineSibling = splineGraph.payload.edgeParentToChildSplines.data[edgeIndexSibling]; SplineMath.Spline splineLeashSibling = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndexSibling]; quaternion siblingRotationParent = splineGraph.payload.rotations.data[siblingVertexIndexParent]; quaternion siblingRotationChild = splineGraph.payload.rotations.data[siblingVertexIndexChild]; SplineMath.FindTFromClosestPointOnSpline(out float siblingClosestT, out float siblingClosestDistance, vertexPositionWS, splineSibling); float3 siblingPositionOnSpline = SplineMath.EvaluatePositionFromT(splineSibling, siblingClosestT); quaternion siblingRotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(splineSibling, siblingRotationParent, siblingRotationChild, siblingClosestT); float2 siblingLeashMaxOS = SplineMath.EvaluatePositionFromT(splineLeashSibling, siblingClosestT).xy; float vertexIntersectionSignedDistance = math.length(math.mul(math.inverse(siblingRotationOnSpline), vertexPositionWS - siblingPositionOnSpline).xy / siblingLeashMaxOS) - 1.0f; vertexIntersectionSignedDistanceMin = math.min(vertexIntersectionSignedDistanceMin, vertexIntersectionSignedDistance); } for (Int16 edgeIndexSibling = vertexChild.parentHead; edgeIndexSibling != -1; edgeIndexSibling = splineGraph.edgePoolParents.data[edgeIndexSibling].next) { DirectedEdge edgeSibling = splineGraph.edgePoolChildren.data[edgeIndexSibling]; Debug.Assert(edgeSibling.IsValid() == 1); if (edgeIndexSibling == edgeIndex) { // Ignore ourselves. continue; } // Found our sibling edge. Only use the first one, as we only currently support CSG against a single branch. Int16 siblingVertexIndexParent = edgeSibling.vertexIndex; Debug.Assert(siblingVertexIndexParent != -1); Int16 siblingVertexIndexChild = splineGraph.edgePoolChildren.data[edgeIndexSibling].vertexIndex; Debug.Assert(siblingVertexIndexChild != -1); SplineMath.Spline splineSibling = splineGraph.payload.edgeParentToChildSplines.data[edgeIndexSibling]; SplineMath.Spline splineLeashSibling = splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndexSibling]; quaternion siblingRotationParent = splineGraph.payload.rotations.data[siblingVertexIndexParent]; quaternion siblingRotationChild = splineGraph.payload.rotations.data[siblingVertexIndexChild]; SplineMath.FindTFromClosestPointOnSpline(out float siblingClosestT, out float siblingClosestDistance, vertexPositionWS, splineSibling); float3 siblingPositionOnSpline = SplineMath.EvaluatePositionFromT(splineSibling, siblingClosestT); quaternion siblingRotationOnSpline = SplineMath.EvaluateRotationWithRollFromT(splineSibling, siblingRotationParent, siblingRotationChild, siblingClosestT); float2 siblingLeashMaxOS = SplineMath.EvaluatePositionFromT(splineLeashSibling, siblingClosestT).xy; float vertexIntersectionSignedDistance = math.length(math.mul(math.inverse(siblingRotationOnSpline), vertexPositionWS - siblingPositionOnSpline).xy / siblingLeashMaxOS) - 1.0f; vertexIntersectionSignedDistanceMin = math.min(vertexIntersectionSignedDistanceMin, vertexIntersectionSignedDistance); } } vertices[meshVertexIndex] = vertexPositionWS; uvs[meshVertexIndex] = new float2( thetaNormalized, vNormalized * splineLength * uvScale // TODO: Gotta figure out propogation of UVs. ); normals[meshVertexIndex] = math.normalize(vertexOffsetWS); float vertexIntersectionDistanceFade = math.smoothstep(vertexIntersectionSignedDistanceFadeMin, vertexIntersectionSignedDistanceFadeMax, vertexIntersectionSignedDistanceMin); colors[meshVertexIndex] = new Color(vertexIntersectionDistanceFade, vertexIntersectionDistanceFade, vertexIntersectionDistanceFade, vertexIntersectionDistanceFade); ++meshVertexIndex; } } } // Trim arrays to final size. Vector3[] verticesTrimmed = new Vector3[meshVertexIndex]; Vector2[] uvsTrimmed = new Vector2[meshVertexIndex]; Vector3[] normalsTrimmed = new Vector3[meshVertexIndex]; Color[] colorsTrimmed = new Color[meshVertexIndex]; int[] trianglesTrimmed = new int[meshTriangleIndex]; Array.Copy(vertices, verticesTrimmed, meshVertexIndex); Array.Copy(uvs, uvsTrimmed, meshVertexIndex); Array.Copy(normals, normalsTrimmed, meshVertexIndex); Array.Copy(colors, colorsTrimmed, meshVertexIndex); Array.Copy(triangles, trianglesTrimmed, meshTriangleIndex); // Finally assign back data (causes GC allocs). mesh.Clear(); mesh.vertices = verticesTrimmed; mesh.uv = uvsTrimmed; mesh.normals = normalsTrimmed; mesh.colors = colorsTrimmed; mesh.triangles = trianglesTrimmed; meshFilter.sharedMesh = mesh; MeshCollider meshCollider = meshFilter.gameObject.GetComponent <MeshCollider>(); if (meshCollider != null) { meshCollider.sharedMesh = mesh; } }
public void Execute(int i) { float positionDelta = math.length(velocities[i]) * deltaTime; SplineMath.SplineGraphFollowState followState = followStates[i]; Unity.Mathematics.Random random = randoms[i]; SplineMath.AdvanceTFromDelta( ref followState, ref random, positionDelta, splineGraph.vertices.data, splineGraph.vertices.count, splineGraph.edgePoolChildren.data, splineGraph.edgePoolParents.data, splineGraph.payload.edgeParentToChildSplines.data, splineGraph.payload.edgeChildToParentSplines.data ); followStates[i] = followState; randoms[i] = random; Int16 edgeIndex = followState.DecodeEdgeIndex(); SplineMath.Spline spline = (followState.DecodeIsReverse() == 0) ? splineGraph.payload.edgeParentToChildSplines.data[edgeIndex] : splineGraph.payload.edgeChildToParentSplines.data[edgeIndex]; Int16 vertexIndexChild = splineGraph.edgePoolChildren.data[edgeIndex].vertexIndex; Int16 vertexIndexParent = splineGraph.edgePoolParents.data[edgeIndex].vertexIndex; if (followState.DecodeIsReverse() == 1) { Int16 vertexIndexTemp = vertexIndexChild; vertexIndexChild = vertexIndexParent; vertexIndexParent = vertexIndexTemp; } quaternion rotationParent = splineGraph.payload.rotations.data[vertexIndexParent]; quaternion rotationChild = splineGraph.payload.rotations.data[vertexIndexChild]; float3 positionPrevious = positions[i]; quaternion rotationPrevious = rotations[i]; float3 positionOnSpline = SplineMath.EvaluatePositionFromT(spline, followState.t); positions[i] = positionOnSpline; // rotations[i] = SplineMath.EvaluateRotationFromT(spline, followState.t); // rotations[i] = math.slerp(rotationParent, rotationChild, followState.t); rotations[i] = SplineMath.EvaluateRotationWithRollFromT(spline, rotationParent, rotationChild, followState.t); // For now, simply evaluate the current leash value by lerping between the parent and child leash values, rather than using spline interpolation. // This seems good enough for now (there is a bug in the spline interpolation code commented out below.) float2 leashParent = splineGraph.payload.leashes.data[vertexIndexParent]; float2 leashChild = splineGraph.payload.leashes.data[vertexIndexChild]; float2 leashMaxOS = math.lerp(leashParent, leashChild, followState.t); // SplineMath.Spline splineLeash = (followState.DecodeIsReverse() == 0) // ? splineGraph.payload.edgeParentToChildSplinesLeashes.data[edgeIndex] // : splineGraph.payload.edgeChildToParentSplinesLeashes.data[edgeIndex]; // float2 leashMaxOS = SplineMath.EvaluatePositionFromT(splineLeash, followState.t).xy; float2 leashOS = leashMaxOS * leashes[i]; float3 leashWS = math.mul(rotations[i], new float3(leashOS, 0.0f)); positions[i] += leashWS; if (avoidanceSoftBodySphereRadius > 1e-5f) { float3 avoidanceDirection = positions[i] - avoidanceSoftBodySphereOrigin; float avoidanceLength = math.length(avoidanceDirection); avoidanceDirection *= (avoidanceLength > 1e-5f) ? (1.0f / avoidanceLength) : 0.0f; float avoidanceOffset = math.saturate((avoidanceLength / avoidanceSoftBodySphereRadius) * -0.5f + 1.0f) * avoidanceSoftBodySphereRadius; if (avoidanceOffset > 0.0f) { float3 leashPlaneNormal = math.mul(rotations[i], new float3(0.0f, 0.0f, 1.0f)); float3 avoidanceDirectionLeashPlaneT = avoidanceDirection - leashPlaneNormal * math.dot(leashPlaneNormal, avoidanceDirection); avoidanceDirectionLeashPlaneT = (math.lengthsq(avoidanceDirectionLeashPlaneT) > 1e-3f) ? avoidanceDirectionLeashPlaneT : leashWS; avoidanceDirectionLeashPlaneT = (math.lengthsq(avoidanceDirectionLeashPlaneT) > 1e-3f) ? avoidanceDirectionLeashPlaneT : new float3(0.0f, 1.0f, 0.0f); avoidanceDirectionLeashPlaneT = math.normalize(avoidanceDirectionLeashPlaneT); positions[i] += avoidanceDirectionLeashPlaneT * avoidanceOffset; } } positions[i] = math.lerp(positions[i], positionPrevious, dampeningPosition); rotations[i] = math.slerp(rotations[i], rotationPrevious, dampeningRotation); // { // float3 acceleration = SplineMath.EvaluateAccelerationFromT(spline, followState.t); // float3 directionRightWS = math.mul(rotations[i], new float3(1.0f, 0.0f, 0.0f)); // float accelerationRight = math.dot(directionRightWS, acceleration); // float rollAngle = math.lerp(-0.25f * math.PI, 0.25f * math.PI, math.saturate((accelerationRight * rollFromAccelerationScale) * -0.5f + 0.5f)); // rotations[i] = math.mul(rotations[i], quaternion.AxisAngle(new float3(0.0f, 0.0f, 1.0f), rollAngle)); // } }