public void Execute(int index) { RangeQueryResult blobNearestNeighbour = BlobNearestNeighbours[index]; for (int j = 0; j < blobNearestNeighbour.Length; j++) { int indexOfOther = blobNearestNeighbour[j]; if (index == indexOfOther) { continue; //ignore self finds. } SpringEdge edge = new SpringEdge(index, indexOfOther); long hash = edge.CustomHashCode(); if (UniqueEdges.Add(hash))//only allow unique EDGES. { // Debug.Log($"Adding unique edge: {edge.A}, { edge.B} " ); Edges.Add(edge.A, edge.B); // Debug.Log($"Adding unique edge: {edge.B}, { edge.A} " ); Edges.Add(edge.B, edge.A); } else { // Debug.Log($"Hash Clash: {edge.A}, { edge.B} " ); } } }
void UpdateRuntimeValues(float deltaTime) { //update cursor friction _jobDataApplyCursorFriction.DeltaTime = deltaTime; _jobDataUpdateCursorPositions.DeltaTime = deltaTime; _jobDataApplyFrictionToBlobs.DeltaTime = deltaTime; _jobDataUpdateBlobPositions.DeltaTime = deltaTime; // _jobDataCopyBlobRadii.Value = GooPhysics.MaxSpringDistance; _jobDataApplyFrictionToBlobs.ConstantFriction = GooPhysics.ConstantFriction; _jobDataApplyFrictionToBlobs.LinearFriction = GooPhysics.ConstantFriction; _jobDataQueryNearestNeighboursKNN.m_SearchRadius = GooPhysics.MaxSpringDistance;//replace with a full copy job bool needsReallocation = GooPhysics.MaxNearestNeighbours != _blobKNNNearestNeighbourQueryResults[0].m_capacity; if (needsReallocation) { Debug.Log("Reallocating knn queries: " + GooPhysics.MaxNearestNeighbours); for (int i = 0; i < _blobKNNNearestNeighbourQueryResults.Length; ++i) { _blobKNNNearestNeighbourQueryResults[i].Dispose(); } for (int i = 0; i < _blobKNNNearestNeighbourQueryResults.Length; ++i) { _blobKNNNearestNeighbourQueryResults[i] = new RangeQueryResult(GooPhysics.MaxNearestNeighbours, Allocator.Persistent); } bNearestNeighboursDirty = true; } _jobDataSpringForcesUsingKnn.SpringConstant = GooPhysics.SpringForce; _jobDataSpringForcesUsingKnn.DampeningConstant = GooPhysics.DampeningConstant; _jobDataSpringForcesUsingKnn.MaxEdgeDistanceRaw = GooPhysics.MaxSpringDistance /* * 2.0f*/; _jobDataSpringForcesUniqueEdges.SpringConstant = GooPhysics.SpringForce; _jobDataSpringForcesUniqueEdges.DampeningConstant = GooPhysics.DampeningConstant; _jobDataSpringForcesUniqueEdges.MaxEdgeDistanceRaw = GooPhysics.MaxSpringDistance /* * 2.0f*/; _jobDataFluidInfluence.InfluenceModulator = GooPhysics.FluidInfluenceModulator; }
//For each blob //Add force based on a nearby blob's velocity. //Also just the blobs close enough for blob influence public void Execute(int index) { float2 blobPos = BlobPositions[index]; float2 blobVel = BlobPositions[index]; RangeQueryResult oneBlobsNearestNeighbours = BlobNearestNeighbours[index]; int numNearestNeighboursToCompare = oneBlobsNearestNeighbours.Length;//may not need this. not sure. float2 accumulateAccel = float2.zero; //note this is n vs n. for (int jIndex = 0; jIndex < numNearestNeighboursToCompare; jIndex++) { int indexOfOtherBlob = oneBlobsNearestNeighbours[jIndex]; if (index == indexOfOtherBlob) { continue; //don't self influence } float2 otherPos = BlobPositions[indexOfOtherBlob]; float2 otherVel = BlobVelocities[indexOfOtherBlob]; float otherRadius = InfluenceRadius[indexOfOtherBlob]; //only care about the difference between our velocities, not the other person's absolute float2 velocityDelta = otherVel - blobVel; float dist = math.distance(blobPos, otherPos);//todo: see if we can use sq distance for falloff. I doubt it but maybe? float distFrac = math.clamp(dist / otherRadius, 0.0f, 1.0f); float invDelta = 1.0f - distFrac; //closer means more force transferred. accumulateAccel += /* dot */ InfluenceModulator * invDelta * velocityDelta; //todo add some kinda proportion control thing. } BlobAccelAccumulator[index] += accumulateAccel; }
private void InitJobData() { float deltaTime = 0f; #region JobDataSetup // //Init all job data here. Declare roughly inline. Optional brackets for things that can be parallel // #region ResetBeginningOfSimFrame _jobDataResetBlobAccelerations = new MemsetNativeArray <float2> { Source = _blobAccelerations, Value = float2.zero }; _jobDataResetCursorAccelerations = new MemsetNativeArray <float2> { Source = _cursorAccelerations, Value = float2.zero }; _jobDataResetGooGroups = new MemsetNativeArray <int> { Source = _blobGroupIDs, Value = -1 }; // _jobDataCopyBlobRadii = new MemsetNativeArray<float> {Source = _blobRadii, Value = GooPhysics.MaxSpringDistance}; _jobDataCopyBlobInfoToFloat3 = new JobCopyBlobInfoToFloat3 { BlobPos = _blobPositions, BlobTeams = _blobTeamIDs, BlobPosFloat3 = _blobPositionsV3 }; _jobBuildKnnTree = new KnnRebuildJob(_knnContainer); // Initialize all the range query results _blobKNNNearestNeighbourQueryResults = new NativeArray <RangeQueryResult>(_blobPositions.Length, Allocator.Persistent); _uniqueBlobEdges = new NativeMultiHashMap <int, int>(_blobPositions.Length * 40, Allocator.Persistent); _uniqueBlobEdgesHashSet = new NativeHashSet <long>(_blobPositions.Length * 40, Allocator.Persistent); // Each range query result object needs to declare upfront what the maximum number of points in range is for (int i = 0; i < _blobKNNNearestNeighbourQueryResults.Length; ++i) { _blobKNNNearestNeighbourQueryResults[i] = new RangeQueryResult(GooPhysics.MaxNearestNeighbours, Allocator.Persistent); } _jobDataQueryNearestNeighboursKNN = new QueryRangesBatchJob { m_container = _knnContainer, m_queryPositions = _blobPositionsV3, m_SearchRadius = GooPhysics.MaxSpringDistance, Results = _blobKNNNearestNeighbourQueryResults }; #endregion //ResetBeginningOfSimFrame #region Updates //build edges with existing positions _jobDataFloodFillGroupIDsKnn = new JobFloodFillIDsKnn() { BlobNearestNeighbours = _blobKNNNearestNeighbourQueryResults, GroupIDs = _blobGroupIDs, FloodQueue = _floodQueue, NumGroups = _numGroups //for safety.don't want divide by zero }; _jobDataFloodFillGroupIDsMultiHashMap = new JobFloodFillIDsUniqueEdges() { Springs = _uniqueBlobEdges, GroupIDs = _blobGroupIDs, FloodQueue = _floodQueue, NumGroups = _numGroups //for safety.don't want divide by zero }; _jobDataSpringForcesUsingKnn = new JobSpringForceUsingKNNResults { AccelerationAccumulator = _blobAccelerations, BlobNearestNeighbours = _blobKNNNearestNeighbourQueryResults, MaxEdgeDistanceRaw = GooPhysics.MaxSpringDistance * 2.0f, SpringConstant = GooPhysics.SpringForce, DampeningConstant = GooPhysics.DampeningConstant, Positions = _blobPositions, Velocity = _blobVelocities, }; _jobCompileDataUniqueEdges = new JobCompileUniqueEdges { BlobNearestNeighbours = _blobKNNNearestNeighbourQueryResults, Edges = _uniqueBlobEdges.AsParallelWriter(), UniqueEdges = _uniqueBlobEdgesHashSet.AsParallelWriter() }; _jobDataSpringForcesUniqueEdges = new JobUniqueSpringForce { AccelerationAccumulator = _blobAccelerations, Springs = _uniqueBlobEdges, MaxEdgeDistanceRaw = GooPhysics.MaxSpringDistance * 2.0f, SpringConstant = GooPhysics.SpringForce, DampeningConstant = GooPhysics.DampeningConstant, Positions = _blobPositions, Velocity = _blobVelocities, }; _jobDataFluidInfluence = new JobVelocityInfluenceFalloff { BlobPositions = _blobPositions, BlobVelocities = _blobVelocities, BlobNearestNeighbours = _blobKNNNearestNeighbourQueryResults, InfluenceRadius = _blobRadii, InfluenceModulator = GooPhysics.FluidInfluenceModulator, BlobAccelAccumulator = _blobAccelerations }; //update cursor accel based on inputs //todo: could be CopyTo? //_cursorInputDeltas.CopyTo(_cursorAccelerations); _jobDataSetCursorAcceleration = new JobSetAcceleration { ValueToSet = _cursorInputDeltas, AccumulatedAcceleration = _cursorAccelerations }; //update cursor friction _jobDataApplyCursorFriction = new JobApplyLinearAndConstantFriction { DeltaTime = deltaTime, LinearFriction = CursorLinearFriction, ConstantFriction = CursorConstantFriction, AccumulatedAcceleration = _cursorAccelerations, Velocity = _cursorVelocities }; _jobDataUpdateCursorPositions = new JobApplyAcceelrationAndVelocity { DeltaTime = deltaTime, AccumulatedAcceleration = _cursorAccelerations, VelocityInAndOut = _cursorVelocities, PositionInAndOut = _cursorPositions }; //Now we can update the blobs with the new state of the cursors _jobDataCursorsInfluenceBlobs = new JobCursorsInfluenceBlobs { CursorPositions = _cursorPositions, CursorVelocities = _cursorVelocities, CursorRadius = _cursorRadii, BlobPositions = _blobPositions, BlobAccelAccumulator = _blobAccelerations }; _jobDataApplyFrictionToBlobs = new JobApplyLinearAndConstantFriction { DeltaTime = deltaTime, //TODO: maybe I want friction based on acceleration (t*t) since that's the freshest part of this. //So, constant + linear(t) + accelerative (t*t) LinearFriction = GooPhysics.LinearFriction, ConstantFriction = GooPhysics.ConstantFriction, AccumulatedAcceleration = _blobAccelerations, Velocity = _blobVelocities }; //Blob sim gets updated _jobDataUpdateBlobPositions = new JobApplyAcceelrationAndVelocity { DeltaTime = deltaTime, AccumulatedAcceleration = _blobAccelerations, VelocityInAndOut = _blobVelocities, PositionInAndOut = _blobPositions }; //Output _jobDataDebugColorisationInt = new JobDebugColorisationInt() { minVal = 0, maxVal = 10, values = _blobGroupIDs, colors = _blobColors, }; _jobDataDebugColorisationKNNLength = new JobDebugColorisationKNNRangeQuery() { minVal = 0, maxVal = 10, values = _blobKNNNearestNeighbourQueryResults, colors = _blobColors, }; /* _jobDataDebugColorisationFloat = new JobDebugColorisationFloat * { * minVal = 0, * maxVal = 10, * values = _blobEdgeCount, * colors =_blobColors, * }*/ _jobDataDebugColorisationFloat2Magnitude = new JobDebugColorisationFloat2XY { maxVal = 10, values = _blobVelocities, colors = _blobColors }; _jobDataCopyBlobsToParticleSystem = new JopCopyBlobsToParticleSystem { colors = _blobColors, positions = _blobPositions, velocities = _blobVelocities }; _jobDataCopyCursorsToTransforms = new JobCopyBlobsToTransforms { BlobPos = _cursorPositions }; #region BoundsForCamera _jobDataCalculateAABB = new JobCalculateAABB() { Positions = _blobPositions, Bounds = _overallGooBounds }; #endregion // BoundsForCamera #endregion // Updates #endregion // JobDataSetup }
// After particle job void LateUpdate() { // Rebuild our datastructure var rebuild = new KnnRebuildJob(m_container); var rebuildHandle = rebuild.Schedule(); // Get all probe positions / colors if (!m_queryPositions.IsCreated || m_queryPositions.Length != QueryProbe.All.Count) { if (m_queryPositions.IsCreated) { m_queryPositions.Dispose(); m_results.Dispose(); m_queryColors.Dispose(); } m_queryPositions = new NativeArray <float3>(QueryProbe.All.Count, Allocator.Persistent); m_results = new NativeArray <int>(QueryK * QueryProbe.All.Count, Allocator.Persistent); m_queryColors = new NativeArray <Color32>(QueryProbe.All.Count, Allocator.Persistent); // Initialize all the range query results m_rangeResults = new NativeArray <RangeQueryResult>(QueryProbe.All.Count, Allocator.Persistent); // Each range query result object needs to declare upfront what the maximum number of points in range is for (int i = 0; i < m_rangeResults.Length; ++i) { // Allow for a maximum of 1024 results m_rangeResults[i] = new RangeQueryResult(1024, Allocator.Persistent); } } for (int i = 0; i < QueryProbe.All.Count; i++) { var p = QueryProbe.All[i]; m_queryPositions[i] = p.transform.position; m_queryColors[i] = p.Color; } switch (Mode) { case QueryMode.KNearest: { // Do a KNN query var query = new QueryKNearestBatchJob(m_container, m_queryPositions, m_results); // Schedule query, dependent on the rebuild // We're only doing a very limited number of points - so allow each query to have it's own job query.ScheduleBatch(m_queryPositions.Length, 1, rebuildHandle).Complete(); break; } case QueryMode.Range: { // Do a range query var query = new QueryRangeBatchJob(m_container, m_queryPositions, QueryRange, m_rangeResults); // Schedule query, dependent on the rebuild // We're only doing a very limited number of points - so allow each query to have it's own job query.Schedule(m_queryPositions.Length, 1, rebuildHandle).Complete(); break; } } }
public static void Demo() { Profiler.BeginSample("Test Query"); // First let's create a random point cloud var points = new NativeArray <float3>(100000, Allocator.Persistent); var rand = new Random(123456); for (int i = 0; i < points.Length; ++i) { points[i] = rand.NextFloat3(); } // Number of neighbours we want to query const int kNeighbours = 10; float3 queryPosition = float3.zero; Profiler.BeginSample("Build"); // Create a container that accelerates querying for neighbours. // The 2nd argument indicates whether we want to build the tree straight away or not // Let's hold off on building it a little bit var knnContainer = new KnnContainer(points, false, Allocator.TempJob); Profiler.EndSample(); // Whenever your point cloud changes, you can make a job to rebuild the container: var rebuildJob = new KnnRebuildJob(knnContainer); rebuildJob.Schedule().Complete(); // Most basic usage: // Get 10 nearest neighbours as indices into our points array! // This is NOT burst accelerated yet! Unity need to implement compiling delegates with Burst var result = new NativeArray <int>(kNeighbours, Allocator.TempJob); knnContainer.QueryKNearest(queryPosition, result); // The result array at this point contains indices into the points array with the nearest neighbours! Profiler.BeginSample("Simple Query"); // Get a job to do the query. var queryJob = new QueryKNearestJob(knnContainer, queryPosition, result); // And just run immediately on the main thread for now. This uses Burst! queryJob.Schedule().Complete(); Profiler.EndSample(); // Or maybe we want to query neighbours for multiple points. const int queryPoints = 100000; // Keep an array of neighbour indices of all points var results = new NativeArray <int>(queryPoints * kNeighbours, Allocator.TempJob); // Query at a few random points var queryPositions = new NativeArray <float3>(queryPoints, Allocator.TempJob); for (int i = 0; i < queryPoints; ++i) { queryPositions[i] = rand.NextFloat3() * 0.1f; } Profiler.BeginSample("Batch Query"); // Fire up job to get results for all points var batchQueryJob = new QueryKNearestBatchJob(knnContainer, queryPositions, results); // And just run immediately now. This will run on multiple threads! batchQueryJob.ScheduleBatch(queryPositions.Length, queryPositions.Length / 32).Complete(); Profiler.EndSample(); // Or maybe we're interested in a range around eacht query point var queryRangeResult = new NativeList <int>(Allocator.TempJob); var rangeQueryJob = new QueryRangeJob(knnContainer, queryPosition, 2.0f, queryRangeResult); // Store a list of particles in range var rangeResults = new NativeArray <RangeQueryResult>(queryPoints, Allocator.TempJob); // And just run immediately on the main thread for now. This uses Burst! rangeQueryJob.Schedule().Complete(); // Unfortunately, for batch range queries we do need to decide upfront the maximum nr. of neighbours we allow // This is due to limitation on allocations within a job. for (int i = 0; i < rangeResults.Length; ++i) { rangeResults[i] = new RangeQueryResult(128, Allocator.TempJob); } Profiler.BeginSample("Batch Range Query"); // Fire up job to get results for all points var batchRange = new QueryRangeBatchJob(knnContainer, queryPositions, 2.0f, rangeResults); // And just run immediately now. This will run on multiple threads! batchRange.Schedule(queryPositions.Length, queryPositions.Length / 32).Complete(); Profiler.EndSample(); // Now the results array contains all the neighbours! queryRangeResult.Dispose(); foreach (var r in rangeResults) { r.Dispose(); } rangeResults.Dispose(); knnContainer.Dispose(); queryPositions.Dispose(); results.Dispose(); points.Dispose(); result.Dispose(); Profiler.EndSample(); }