添加插件
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstAabb
|
||||
{
|
||||
public float4 min;
|
||||
public float4 max;
|
||||
|
||||
public float4 size
|
||||
{
|
||||
get { return max - min; }
|
||||
}
|
||||
|
||||
public float4 center
|
||||
{
|
||||
get { return min + (max - min) * 0.5f; }
|
||||
}
|
||||
|
||||
public BurstAabb(float4 min, float4 max)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public BurstAabb(float4 v1, float4 v2, float4 v3, float4 margin)
|
||||
{
|
||||
min = math.min(math.min(v1, v2), v3) - margin;
|
||||
max = math.max(math.max(v1, v2), v3) + margin;
|
||||
}
|
||||
|
||||
public BurstAabb(float4 v1, float4 v2, float4 margin)
|
||||
{
|
||||
min = math.min(v1, v2) - margin;
|
||||
max = math.max(v1, v2) + margin;
|
||||
}
|
||||
|
||||
public BurstAabb(float4 previousPosition, float4 position, float radius)
|
||||
{
|
||||
min = math.min(position - radius, previousPosition - radius);
|
||||
max = math.max(position + radius, previousPosition + radius);
|
||||
}
|
||||
|
||||
public float AverageAxisLength()
|
||||
{
|
||||
float4 d = max - min;
|
||||
return (d.x + d.y + d.z) * 0.33f;
|
||||
}
|
||||
|
||||
public float MaxAxisLength()
|
||||
{
|
||||
return math.cmax((max - min).xyz);
|
||||
}
|
||||
|
||||
public void EncapsulateParticle(float4 position, float radius)
|
||||
{
|
||||
min = math.min(min, position - radius);
|
||||
max = math.max(max, position + radius);
|
||||
}
|
||||
|
||||
public void EncapsulateParticle(float4 previousPosition, float4 position, float radius)
|
||||
{
|
||||
min = math.min(math.min(min, position - radius), previousPosition - radius);
|
||||
max = math.max(math.max(max, position + radius), previousPosition + radius);
|
||||
}
|
||||
|
||||
public void EncapsulateBounds(in BurstAabb bounds)
|
||||
{
|
||||
min = math.min(min,bounds.min);
|
||||
max = math.max(max,bounds.max);
|
||||
}
|
||||
|
||||
public void Expand(float4 amount)
|
||||
{
|
||||
min -= amount;
|
||||
max += amount;
|
||||
}
|
||||
|
||||
public void Sweep(float4 velocity)
|
||||
{
|
||||
min = math.min(min, min + velocity);
|
||||
max = math.max(max, max + velocity);
|
||||
}
|
||||
|
||||
public void Transform(in BurstAffineTransform transform)
|
||||
{
|
||||
Transform(float4x4.TRS(transform.translation.xyz, transform.rotation, transform.scale.xyz));
|
||||
}
|
||||
|
||||
public void Transform(in float4x4 transform)
|
||||
{
|
||||
float3 xa = transform.c0.xyz * min.x;
|
||||
float3 xb = transform.c0.xyz * max.x;
|
||||
|
||||
float3 ya = transform.c1.xyz * min.y;
|
||||
float3 yb = transform.c1.xyz * max.y;
|
||||
|
||||
float3 za = transform.c2.xyz * min.z;
|
||||
float3 zb = transform.c2.xyz * max.z;
|
||||
|
||||
min = new float4(math.min(xa, xb) + math.min(ya, yb) + math.min(za, zb) + transform.c3.xyz, 0);
|
||||
max = new float4(math.max(xa, xb) + math.max(ya, yb) + math.max(za, zb) + transform.c3.xyz, 0);
|
||||
}
|
||||
|
||||
public BurstAabb Transformed(in BurstAffineTransform transform)
|
||||
{
|
||||
var cpy = this;
|
||||
cpy.Transform(transform);
|
||||
return cpy;
|
||||
}
|
||||
|
||||
public BurstAabb Transformed(in float4x4 transform)
|
||||
{
|
||||
var cpy = this;
|
||||
cpy.Transform(transform);
|
||||
return cpy;
|
||||
}
|
||||
|
||||
public bool IntersectsAabb(in BurstAabb bounds, bool in2D = false)
|
||||
{
|
||||
if (in2D)
|
||||
return (min[0] <= bounds.max[0] && max[0] >= bounds.min[0]) &&
|
||||
(min[1] <= bounds.max[1] && max[1] >= bounds.min[1]);
|
||||
|
||||
return (min[0] <= bounds.max[0] && max[0] >= bounds.min[0]) &&
|
||||
(min[1] <= bounds.max[1] && max[1] >= bounds.min[1]) &&
|
||||
(min[2] <= bounds.max[2] && max[2] >= bounds.min[2]);
|
||||
}
|
||||
|
||||
public bool IntersectsRay(float4 origin, float4 inv_dir, bool in2D = false)
|
||||
{
|
||||
float4 t1 = (min - origin) * inv_dir;
|
||||
float4 t2 = (max - origin) * inv_dir;
|
||||
|
||||
float4 tmin1 = math.min(t1,t2);
|
||||
float4 tmax1 = math.max(t1,t2);
|
||||
|
||||
float tmin, tmax;
|
||||
|
||||
if (in2D)
|
||||
{
|
||||
tmin = math.cmax(tmin1.xy);
|
||||
tmax = math.cmin(tmax1.xy);
|
||||
}
|
||||
else
|
||||
{
|
||||
tmin = math.cmax(tmin1.xyz);
|
||||
tmax = math.cmin(tmax1.xyz);
|
||||
}
|
||||
|
||||
return tmax >= math.max(0, tmin) && tmin <= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2766a51cbde7a4fe490989456f737d23
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,95 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstAffineTransform
|
||||
{
|
||||
public float4 translation;
|
||||
public float4 scale;
|
||||
public quaternion rotation;
|
||||
|
||||
public BurstAffineTransform(float4 translation, quaternion rotation, float4 scale)
|
||||
{
|
||||
// make sure there are good values in the 4th component:
|
||||
translation[3] = 0;
|
||||
scale[3] = 1;
|
||||
|
||||
this.translation = translation;
|
||||
this.rotation = rotation;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public static BurstAffineTransform operator *(BurstAffineTransform a, BurstAffineTransform b)
|
||||
{
|
||||
return new BurstAffineTransform(a.TransformPoint(b.translation),
|
||||
math.mul(a.rotation,b.rotation),
|
||||
a.scale * b.scale);
|
||||
}
|
||||
|
||||
public BurstAffineTransform Inverse()
|
||||
{
|
||||
return new BurstAffineTransform(new float4(math.rotate(math.conjugate(rotation),(translation / -scale).xyz),0),
|
||||
math.conjugate(rotation),
|
||||
1 / scale);
|
||||
}
|
||||
|
||||
public BurstAffineTransform Integrate(float4 linearVelocity, float4 angularVelocity, float dt)
|
||||
{
|
||||
return new BurstAffineTransform(BurstIntegration.IntegrateLinear(translation, linearVelocity, dt),
|
||||
BurstIntegration.IntegrateAngular(rotation, angularVelocity, dt),
|
||||
scale);
|
||||
}
|
||||
|
||||
public BurstAffineTransform Interpolate(BurstAffineTransform other, float translationalMu, float rotationalMu, float scaleMu)
|
||||
{
|
||||
return new BurstAffineTransform(math.lerp(translation, other.translation, translationalMu),
|
||||
math.slerp(rotation, other.rotation, rotationalMu),
|
||||
math.lerp(scale, other.scale, scaleMu));
|
||||
}
|
||||
|
||||
public float4 TransformPoint(float4 point)
|
||||
{
|
||||
return new float4(translation.xyz + math.rotate(rotation, (point * scale).xyz),0);
|
||||
}
|
||||
|
||||
public float4 InverseTransformPoint(float4 point)
|
||||
{
|
||||
return new float4(math.rotate(math.conjugate(rotation),(point - translation).xyz) / scale.xyz , 0);
|
||||
}
|
||||
|
||||
public float4 TransformPointUnscaled(float4 point)
|
||||
{
|
||||
return new float4(translation.xyz + math.rotate(rotation,point.xyz), 0);
|
||||
}
|
||||
|
||||
public float4 InverseTransformPointUnscaled(float4 point)
|
||||
{
|
||||
return new float4(math.rotate(math.conjugate(rotation), (point - translation).xyz), 0);
|
||||
}
|
||||
|
||||
|
||||
public float4 TransformDirection(float4 direction)
|
||||
{
|
||||
return new float4(math.rotate(rotation, direction.xyz), 0);
|
||||
}
|
||||
|
||||
public float4 InverseTransformDirection(float4 direction)
|
||||
{
|
||||
return new float4(math.rotate(math.conjugate(rotation), direction.xyz), 0);
|
||||
}
|
||||
|
||||
public float4 TransformVector(float4 vector)
|
||||
{
|
||||
return new float4(math.rotate(rotation, (vector * scale).xyz), 0);
|
||||
}
|
||||
|
||||
public float4 InverseTransformVector(float4 vector)
|
||||
{
|
||||
return new float4(math.rotate(math.conjugate(rotation),vector.xyz) / scale.xyz, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42d935e53ff25405bb572c630431a08e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,60 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstCellSpan : IEquatable<BurstCellSpan>
|
||||
{
|
||||
public int4 min;
|
||||
public int4 max;
|
||||
|
||||
|
||||
public BurstCellSpan(CellSpan span)
|
||||
{
|
||||
this.min = new int4(span.min.x, span.min.y, span.min.z, span.min.w);
|
||||
this.max = new int4(span.max.x, span.max.y, span.max.z, span.max.w);
|
||||
}
|
||||
|
||||
public BurstCellSpan(int4 min, int4 max)
|
||||
{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
|
||||
public int level
|
||||
{
|
||||
get{return min.w;}
|
||||
}
|
||||
|
||||
public bool Equals(BurstCellSpan other)
|
||||
{
|
||||
return min.Equals(other.min) && max.Equals(other.max);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return this.Equals((BurstCellSpan)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0; // we don't have any non-mutable fields, so just return 0.
|
||||
}
|
||||
|
||||
public static bool operator ==(BurstCellSpan a, BurstCellSpan b)
|
||||
{
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
public static bool operator !=(BurstCellSpan a, BurstCellSpan b)
|
||||
{
|
||||
return !a.Equals(b);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9612a967e340a428ba9353b922a881be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstCollisionMaterial // TODO: use CollisionMaterial directly.
|
||||
{
|
||||
public float dynamicFriction;
|
||||
public float staticFriction;
|
||||
public float rollingFriction;
|
||||
public float stickiness;
|
||||
public float stickDistance;
|
||||
public Oni.MaterialCombineMode frictionCombine;
|
||||
public Oni.MaterialCombineMode stickinessCombine;
|
||||
public int rollingContacts;
|
||||
|
||||
public static BurstCollisionMaterial CombineWith(BurstCollisionMaterial a, BurstCollisionMaterial b)
|
||||
{
|
||||
BurstCollisionMaterial result = new BurstCollisionMaterial();
|
||||
var frictionCombineMode = (Oni.MaterialCombineMode)math.max((int)a.frictionCombine, (int)b.frictionCombine);
|
||||
var stickCombineMode = (Oni.MaterialCombineMode)math.max((int)a.stickinessCombine, (int)b.stickinessCombine);
|
||||
|
||||
switch (frictionCombineMode)
|
||||
{
|
||||
default: // average
|
||||
result.dynamicFriction = (a.dynamicFriction + b.dynamicFriction) * 0.5f;
|
||||
result.staticFriction = (a.staticFriction + b.staticFriction) * 0.5f;
|
||||
result.rollingFriction = (a.rollingFriction + b.rollingFriction) * 0.5f;
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Minimum:
|
||||
result.dynamicFriction = math.min(a.dynamicFriction, b.dynamicFriction);
|
||||
result.staticFriction = math.min(a.staticFriction, b.staticFriction);
|
||||
result.rollingFriction = math.min(a.rollingFriction, b.rollingFriction);
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Multiply:
|
||||
result.dynamicFriction = a.dynamicFriction * b.dynamicFriction;
|
||||
result.staticFriction = a.staticFriction * b.staticFriction;
|
||||
result.rollingFriction = a.rollingFriction * b.rollingFriction;
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Maximum:
|
||||
result.dynamicFriction = math.max(a.dynamicFriction, b.dynamicFriction);
|
||||
result.staticFriction = math.max(a.staticFriction, b.staticFriction);
|
||||
result.rollingFriction = math.max(a.rollingFriction, b.rollingFriction);
|
||||
break;
|
||||
}
|
||||
|
||||
switch (stickCombineMode)
|
||||
{
|
||||
default: // average
|
||||
result.stickiness = (a.stickiness + b.stickiness) * 0.5f;
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Minimum:
|
||||
result.stickiness = math.min(a.stickiness, b.stickiness);
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Multiply:
|
||||
result.stickiness = a.stickiness * b.stickiness;
|
||||
break;
|
||||
|
||||
case Oni.MaterialCombineMode.Maximum:
|
||||
result.stickiness = math.max(a.stickiness, b.stickiness);
|
||||
break;
|
||||
}
|
||||
|
||||
result.stickDistance = math.max(a.stickDistance, b.stickDistance);
|
||||
result.rollingContacts = a.rollingContacts | b.rollingContacts;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de388c94a975a489a8093d110d42af2a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,65 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using UnityEngine;
|
||||
using Unity.Mathematics;
|
||||
using System.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstInertialFrame
|
||||
{
|
||||
public BurstAffineTransform frame;
|
||||
public BurstAffineTransform prevFrame;
|
||||
|
||||
public float4 velocity;
|
||||
public float4 angularVelocity;
|
||||
|
||||
public float4 acceleration;
|
||||
public float4 angularAcceleration;
|
||||
|
||||
public BurstInertialFrame(float4 position, float4 scale, quaternion rotation)
|
||||
{
|
||||
this.frame = new BurstAffineTransform(position, rotation, scale);
|
||||
this.prevFrame = frame;
|
||||
|
||||
velocity = float4.zero;
|
||||
angularVelocity = float4.zero;
|
||||
acceleration = float4.zero;
|
||||
angularAcceleration = float4.zero;
|
||||
}
|
||||
|
||||
public BurstInertialFrame(BurstAffineTransform frame)
|
||||
{
|
||||
this.frame = frame;
|
||||
this.prevFrame = frame;
|
||||
|
||||
velocity = float4.zero;
|
||||
angularVelocity = float4.zero;
|
||||
acceleration = float4.zero;
|
||||
angularAcceleration = float4.zero;
|
||||
}
|
||||
|
||||
public float4 VelocityAtPoint(float4 point)
|
||||
{
|
||||
return velocity + new float4(math.cross(angularVelocity.xyz, (point - prevFrame.translation).xyz), 0);
|
||||
}
|
||||
|
||||
public void Update(float4 position, float4 scale, quaternion rotation, float dt)
|
||||
{
|
||||
prevFrame = frame;
|
||||
float4 prevVelocity = velocity;
|
||||
float4 prevAngularVelocity = angularVelocity;
|
||||
|
||||
frame.translation = position;
|
||||
frame.rotation = rotation;
|
||||
frame.scale = scale;
|
||||
|
||||
velocity = BurstIntegration.DifferentiateLinear(frame.translation, prevFrame.translation, dt);
|
||||
angularVelocity = BurstIntegration.DifferentiateAngular(frame.rotation, prevFrame.rotation, dt);
|
||||
|
||||
acceleration = BurstIntegration.DifferentiateLinear(velocity, prevVelocity, dt);
|
||||
angularAcceleration = BurstIntegration.DifferentiateLinear(angularVelocity, prevAngularVelocity, dt);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e3c3b9a94006d4984a94f040e6d2c737
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,128 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System.Collections.Generic;
|
||||
using Unity.Burst;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class BurstPrefixSum
|
||||
{
|
||||
private int inputSize;
|
||||
private const int numBlocks = 8;
|
||||
|
||||
private NativeArray<int> blockSums;
|
||||
|
||||
public BurstPrefixSum(int inputSize)
|
||||
{
|
||||
this.inputSize = inputSize;
|
||||
blockSums = new NativeArray<int>(numBlocks, Allocator.Persistent);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (blockSums.IsCreated)
|
||||
blockSums.Dispose();
|
||||
}
|
||||
|
||||
public unsafe JobHandle Sum(NativeArray<int> input, NativeArray<int> result, int* count, JobHandle inputDeps)
|
||||
{
|
||||
|
||||
// calculate partial prefix sums, one per block:
|
||||
var job = new BlockSumJob
|
||||
{
|
||||
input = input,
|
||||
output = result,
|
||||
blocks = blockSums,
|
||||
count = count
|
||||
};
|
||||
inputDeps = job.Schedule(numBlocks, 1, inputDeps);
|
||||
|
||||
var job3 = new BlockSum
|
||||
{
|
||||
blocks = blockSums
|
||||
};
|
||||
inputDeps = job3.Schedule(inputDeps);
|
||||
|
||||
// add the scanned partial block sums to the result:
|
||||
var job2 = new PrefixSumJob
|
||||
{
|
||||
prefixBlocks = blockSums,
|
||||
output = result,
|
||||
count = count
|
||||
};
|
||||
return job2.Schedule(numBlocks, 1, inputDeps);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
unsafe struct BlockSumJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<int> input;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<int> output;
|
||||
public NativeArray<int> blocks;
|
||||
|
||||
[ReadOnly] [NativeDisableUnsafePtrRestriction] public int* count;
|
||||
|
||||
public void Execute(int block)
|
||||
{
|
||||
int length = *count + 1; // add 1 to get total sum in last element+1
|
||||
int blockSize = (int)math.ceil(length / (float)numBlocks);
|
||||
|
||||
int start = block * blockSize;
|
||||
int end = math.min(start + blockSize, length);
|
||||
|
||||
output[start] = 0;
|
||||
|
||||
if (blockSize == 0) { blocks[block] = 0; return; }
|
||||
|
||||
for (int i = start + 1; i < end; ++i)
|
||||
output[i] = output[i - 1] + input[i - 1];
|
||||
|
||||
blocks[block] = output[end - 1] + input[end - 1];
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
struct BlockSum : IJob
|
||||
{
|
||||
public NativeArray<int> blocks;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
int aux = blocks[0];
|
||||
blocks[0] = 0;
|
||||
|
||||
for (int i = 1; i < blocks.Length; ++i)
|
||||
{
|
||||
int a = blocks[i];
|
||||
blocks[i] = blocks[i - 1] + aux;
|
||||
aux = a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
unsafe struct PrefixSumJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeArray<int> prefixBlocks;
|
||||
[NativeDisableParallelForRestriction] public NativeArray<int> output;
|
||||
|
||||
[ReadOnly] [NativeDisableUnsafePtrRestriction] public int* count;
|
||||
|
||||
public void Execute(int block)
|
||||
{
|
||||
int length = *count + 1; // add 1 to get total sum in last element+1
|
||||
int blockSize = (int)math.ceil(length / (float)numBlocks);
|
||||
|
||||
int start = block * blockSize;
|
||||
int end = math.min(start + blockSize, length);
|
||||
|
||||
for (int i = start; i < end; ++i)
|
||||
output[i] += prefixBlocks[block];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fa11563c202445279d5e04b0eb1631b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,16 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstQueryShape
|
||||
{
|
||||
public float4 center;
|
||||
public float4 size;
|
||||
public QueryShape.QueryType type;
|
||||
public float contactOffset;
|
||||
public float maxDistance;
|
||||
public int filter;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1407a04be79f44be6a770255e8e89195
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
public struct BurstRigidbody
|
||||
{
|
||||
public float4x4 inverseInertiaTensor;
|
||||
public float4 velocity;
|
||||
public float4 angularVelocity;
|
||||
public float4 com;
|
||||
public float inverseMass;
|
||||
|
||||
public int constraintCount;
|
||||
private int pad1;
|
||||
private int pad2;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f72992ee9e3174e89b3c0e5b6da57a17
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20b822fd4fe494009afa17025c273ec8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BatchLUT : IDisposable
|
||||
{
|
||||
public readonly int numBatches;
|
||||
public readonly NativeArray<ushort> batchIndex;
|
||||
|
||||
public BatchLUT(int numBatches)
|
||||
{
|
||||
this.numBatches = numBatches;
|
||||
|
||||
batchIndex = new NativeArray<ushort>(UInt16.MaxValue + 1, Allocator.Persistent, NativeArrayOptions.ClearMemory);
|
||||
const ushort end = UInt16.MaxValue;
|
||||
ushort numBits = (ushort)(numBatches - 1);
|
||||
|
||||
// For each entry in the table, compute the position of the first '0' bit in the index, starting from the less significant bit.
|
||||
// This is the index of the first batch where we can add the constraint to.
|
||||
|
||||
for (ushort value = 0; value < end; value++)
|
||||
{
|
||||
ushort valueCopy = value;
|
||||
for (ushort i = 0; i < numBits; i++)
|
||||
{
|
||||
if ((valueCopy & 1) == 0)
|
||||
{
|
||||
batchIndex[value] = i;
|
||||
break;
|
||||
}
|
||||
valueCopy >>= 1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
batchIndex[end] = numBits;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
batchIndex.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ba613bda78be4a71be94b90f92fbde5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,216 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Burst;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
public struct BatchData
|
||||
{
|
||||
public ushort batchID; // Batch identifier. All bits will be '0', except for the one at the position of the batch.
|
||||
|
||||
public int startIndex; // first constraint in the batch
|
||||
public int constraintCount; // amount of constraints in the batch.
|
||||
public int activeConstraintCount; // auxiliar counter used to sort the constraints in linear time.
|
||||
|
||||
public int workItemSize; // size of each work item.
|
||||
public int workItemCount; // number of work items.
|
||||
public bool isLast;
|
||||
|
||||
public BatchData(int index, int maxBatches)
|
||||
{
|
||||
batchID = (ushort)(1 << index);
|
||||
isLast = index == (maxBatches - 1);
|
||||
constraintCount = 0;
|
||||
activeConstraintCount = 0;
|
||||
|
||||
startIndex = 0;
|
||||
workItemSize = 0;
|
||||
workItemCount = 0;
|
||||
}
|
||||
|
||||
public void GetConstraintRange(int workItemIndex, out int start, out int end)
|
||||
{
|
||||
start = startIndex + workItemSize * workItemIndex;
|
||||
end = startIndex + math.min(constraintCount, workItemSize * (workItemIndex + 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public unsafe struct WorkItem
|
||||
{
|
||||
public const int minWorkItemSize = 64;
|
||||
public fixed int constraints[minWorkItemSize];
|
||||
public int constraintCount;
|
||||
|
||||
public bool Add(int constraintIndex)
|
||||
{
|
||||
// add the constraint to this work item.
|
||||
fixed (int* constraintIndices = constraints)
|
||||
{
|
||||
constraintIndices[constraintCount] = constraintIndex;
|
||||
}
|
||||
|
||||
// if we've completed the work item, close it and reuse for the next one.
|
||||
return (++constraintCount == minWorkItemSize);
|
||||
}
|
||||
}
|
||||
|
||||
public struct ConstraintBatcher<T> : IDisposable where T : struct, IConstraintProvider
|
||||
{
|
||||
public int maxBatches;
|
||||
private BatchLUT batchLUT; // look up table for batch indices.
|
||||
|
||||
public ConstraintBatcher(int maxBatches)
|
||||
{
|
||||
this.maxBatches = math.min(17, maxBatches);
|
||||
batchLUT = new BatchLUT(this.maxBatches);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
batchLUT.Dispose();
|
||||
}
|
||||
|
||||
/**
|
||||
* Linear-time graph coloring using bitmasks and a look-up table. Used to organize contacts into batches for parallel processing.
|
||||
* input: array of unsorted constraints.
|
||||
* output:
|
||||
* - sorted constraint indices array.
|
||||
* - array of batchData, one per batch: startIndex, batchSize, workItemSize (at most == batchSize), numWorkItems
|
||||
* - number of active batches.
|
||||
*/
|
||||
|
||||
public JobHandle BatchConstraints(ref T constraintDesc,
|
||||
int particleCount,
|
||||
ref NativeArray<BatchData> batchData,
|
||||
ref NativeArray<int> activeBatchCount,
|
||||
JobHandle inputDeps)
|
||||
{
|
||||
if (activeBatchCount.Length != 1)
|
||||
return inputDeps;
|
||||
|
||||
var batchJob = new BatchContactsJob
|
||||
{
|
||||
batchMasks = new NativeArray<ushort>(particleCount, Allocator.TempJob, NativeArrayOptions.ClearMemory),
|
||||
batchIndices = new NativeArray<int>(constraintDesc.GetConstraintCount(), Allocator.TempJob, NativeArrayOptions.ClearMemory),
|
||||
lut = batchLUT,
|
||||
constraintDesc = constraintDesc,
|
||||
batchData = batchData,
|
||||
activeBatchCount = activeBatchCount,
|
||||
maxBatches = maxBatches
|
||||
};
|
||||
|
||||
return batchJob.Schedule(inputDeps);
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct BatchContactsJob : IJob
|
||||
{
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<ushort> batchMasks;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
public NativeArray<int> batchIndices;
|
||||
|
||||
[ReadOnly] public BatchLUT lut;
|
||||
public T constraintDesc;
|
||||
public NativeArray<BatchData> batchData;
|
||||
public NativeArray<int> activeBatchCount;
|
||||
|
||||
public int maxBatches;
|
||||
|
||||
public unsafe void Execute()
|
||||
{
|
||||
// Initialize batch data array
|
||||
for (int i = 0; i < batchData.Length; ++i)
|
||||
batchData[i] = new BatchData(i, maxBatches);
|
||||
|
||||
// temporary array containing an open work item for each batch.
|
||||
WorkItem* workItems = stackalloc WorkItem[maxBatches];
|
||||
for (int i = 0; i < maxBatches; i++)
|
||||
workItems[i] = new WorkItem();
|
||||
|
||||
int constraintCount = constraintDesc.GetConstraintCount();
|
||||
|
||||
// find a batch for each constraint:
|
||||
for (int i = 0; i < constraintCount; ++i)
|
||||
{
|
||||
// OR together the batch masks of all entities involved in the constraint:
|
||||
int batchMask = 0;
|
||||
for (int k = 0; k < constraintDesc.GetParticleCount(i); ++k)
|
||||
batchMask |= batchMasks[constraintDesc.GetParticle(i, k)];
|
||||
|
||||
// look up the first free batch index for this constraint:
|
||||
int batchIndex = batchIndices[i] = lut.batchIndex[batchMask];
|
||||
|
||||
// update the amount of constraints in the batch:
|
||||
var batch = batchData[batchIndex];
|
||||
batch.constraintCount++;
|
||||
batchData[batchIndex] = batch;
|
||||
|
||||
// add the constraint to the last work item of the batch:
|
||||
if (workItems[batchIndex].Add(i))
|
||||
{
|
||||
// if this work item does not belong to the last batch:
|
||||
if (batchIndex != maxBatches - 1)
|
||||
{
|
||||
// tag all entities in the work item with the batch mask to close it.
|
||||
// this way we know constraints referencing any of these entities can no longer be added to this batch.
|
||||
for (int j = 0; j < workItems[batchIndex].constraintCount; j++)
|
||||
{
|
||||
int constraint = workItems[batchIndex].constraints[j];
|
||||
|
||||
for (int k = 0; k < constraintDesc.GetParticleCount(constraint); ++k)
|
||||
batchMasks[constraintDesc.GetParticle(constraint, k)] |= batch.batchID;
|
||||
}
|
||||
}
|
||||
|
||||
// reuse the work item.
|
||||
workItems[batchIndex].constraintCount = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// fill batch data:
|
||||
activeBatchCount[0] = 0;
|
||||
int numConstraints = 0;
|
||||
for (int i = 0; i < batchData.Length; ++i)
|
||||
{
|
||||
var batch = batchData[i];
|
||||
|
||||
// bail out when we find the first empty batch:
|
||||
if (batch.constraintCount == 0)
|
||||
break;
|
||||
|
||||
// calculate work item size, count, and index of the first constraint
|
||||
batch.workItemSize = math.min(WorkItem.minWorkItemSize, batch.constraintCount);
|
||||
batch.workItemCount = (batch.constraintCount + batch.workItemSize - 1) / batch.workItemSize;
|
||||
batch.startIndex = numConstraints;
|
||||
|
||||
numConstraints += batch.constraintCount;
|
||||
activeBatchCount[0]++;
|
||||
|
||||
batchData[i] = batch;
|
||||
}
|
||||
|
||||
// write out sorted constraint indices:
|
||||
for (int i = 0; i < constraintCount; ++i)
|
||||
{
|
||||
var batch = batchData[batchIndices[i]];
|
||||
int sortedIndex = batch.startIndex + (batch.activeConstraintCount++);
|
||||
constraintDesc.WriteSortedConstraint(i, sortedIndex);
|
||||
batchData[batchIndices[i]] = batch;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f15c420c952f4f689684489a7de8920
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,160 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Burst;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public class ConstraintSorter<T> where T : unmanaged, IConstraint
|
||||
{
|
||||
|
||||
public struct ConstraintComparer<K> : IComparer<K> where K : IConstraint
|
||||
{
|
||||
// Compares by Height, Length, and Width.
|
||||
public int Compare(K x, K y)
|
||||
{
|
||||
return x.GetParticle(1).CompareTo(y.GetParticle(1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a single-threaded count sort on the constraints array using the first particle index,
|
||||
* then multiple parallel sorts over slices of the original array sorting by the second particle index.
|
||||
*/
|
||||
public JobHandle SortConstraints(int particleCount,
|
||||
NativeArray<T> constraints,
|
||||
ref NativeArray<T> sortedConstraints,
|
||||
JobHandle handle)
|
||||
{
|
||||
// Count the amount of digits in the largest particle index that can be referenced by a constraint:
|
||||
NativeArray<int> totalCountUpToDigit = new NativeArray<int>(particleCount + 1, Allocator.TempJob);
|
||||
int numDigits = 0;
|
||||
int maxBodyIndex = particleCount - 1;
|
||||
{
|
||||
int val = maxBodyIndex;
|
||||
while (val > 0)
|
||||
{
|
||||
val >>= 1;
|
||||
numDigits++;
|
||||
}
|
||||
}
|
||||
|
||||
handle = new CountSortPerFirstParticleJob
|
||||
{
|
||||
input = constraints,
|
||||
output = sortedConstraints,
|
||||
maxDigits = numDigits,
|
||||
maxIndex = maxBodyIndex,
|
||||
digitCount = totalCountUpToDigit
|
||||
}.Schedule(handle);
|
||||
|
||||
// Sort sub arrays with default sort.
|
||||
int numPerBatch = math.max(1, maxBodyIndex / 32);
|
||||
|
||||
handle = new SortSubArraysJob
|
||||
{
|
||||
InOutArray = sortedConstraints,
|
||||
NextElementIndex = totalCountUpToDigit,
|
||||
comparer = new ConstraintComparer<T>()
|
||||
}.Schedule(totalCountUpToDigit.Length, numPerBatch, handle);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public struct CountSortPerFirstParticleJob : IJob
|
||||
{
|
||||
[ReadOnly][NativeDisableContainerSafetyRestriction] public NativeArray<T> input;
|
||||
public NativeArray<T> output;
|
||||
|
||||
[NativeDisableContainerSafetyRestriction] public NativeArray<int> digitCount;
|
||||
|
||||
public int maxDigits;
|
||||
public int maxIndex;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
// no real need for a mask, just in case bad particle indices were passed that have more digits than maxDigits.
|
||||
int mask = (1 << maxDigits) - 1;
|
||||
|
||||
// Count digits
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
digitCount[input[i].GetParticle(0) & mask]++;
|
||||
}
|
||||
|
||||
// Calculate start index for each digit
|
||||
int prev = digitCount[0];
|
||||
digitCount[0] = 0;
|
||||
for (int i = 1; i <= maxIndex; i++)
|
||||
{
|
||||
int current = digitCount[i];
|
||||
digitCount[i] = digitCount[i - 1] + prev;
|
||||
prev = current;
|
||||
}
|
||||
|
||||
// Copy elements into buckets based on particle index
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
int index = digitCount[input[i].GetParticle(0) & mask]++;
|
||||
if (index == 1 && input.Length == 1)
|
||||
{
|
||||
output[0] = input[0];
|
||||
}
|
||||
output[index] = input[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sorts slices of an array in parallel
|
||||
[BurstCompile]
|
||||
public struct SortSubArraysJob : IJobParallelFor
|
||||
{
|
||||
[NativeDisableContainerSafetyRestriction] public NativeArray<T> InOutArray;
|
||||
|
||||
// Typically lastDigitIndex is resulting RadixSortPerBodyAJob.digitCount. nextElementIndex[i] = index of first element with bodyA index == i + 1
|
||||
[NativeDisableContainerSafetyRestriction][DeallocateOnJobCompletion] public NativeArray<int> NextElementIndex;
|
||||
|
||||
[ReadOnly] public ConstraintComparer<T> comparer;
|
||||
|
||||
public void Execute(int workItemIndex)
|
||||
{
|
||||
int startIndex = 0;
|
||||
if (workItemIndex > 0)
|
||||
{
|
||||
startIndex = NextElementIndex[workItemIndex - 1];
|
||||
}
|
||||
|
||||
if (startIndex < InOutArray.Length)
|
||||
{
|
||||
int length = NextElementIndex[workItemIndex] - startIndex;
|
||||
DefaultSortOfSubArrays(InOutArray, startIndex, length, comparer);
|
||||
}
|
||||
}
|
||||
|
||||
public static void DefaultSortOfSubArrays(NativeArray<T> inOutArray, int startIndex, int length, ConstraintComparer<T> comparer)
|
||||
{
|
||||
if (length > 2)
|
||||
{
|
||||
var slice = inOutArray.Slice(startIndex, length);
|
||||
slice.Sort(comparer);
|
||||
}
|
||||
else if (length == 2) // just swap:
|
||||
{
|
||||
if (inOutArray[startIndex].GetParticle(1) > inOutArray[startIndex + 1].GetParticle(1))
|
||||
{
|
||||
var temp = inOutArray[startIndex + 1];
|
||||
inOutArray[startIndex + 1] = inOutArray[startIndex];
|
||||
inOutArray[startIndex] = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4cb7051d5b32442d79da18c47c1fbcc2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct ContactProvider : IConstraintProvider
|
||||
{
|
||||
public NativeArray<BurstContact> contacts;
|
||||
public NativeArray<BurstContact> sortedContacts;
|
||||
public NativeArray<int> simplices;
|
||||
public SimplexCounts simplexCounts;
|
||||
|
||||
public int GetConstraintCount()
|
||||
{
|
||||
return contacts.Length;
|
||||
}
|
||||
|
||||
public int GetParticleCount(int constraintIndex)
|
||||
{
|
||||
simplexCounts.GetSimplexStartAndSize(contacts[constraintIndex].bodyA, out int simplexSizeA);
|
||||
simplexCounts.GetSimplexStartAndSize(contacts[constraintIndex].bodyB, out int simplexSizeB);
|
||||
return simplexSizeA + simplexSizeB;
|
||||
}
|
||||
public int GetParticle(int constraintIndex, int index)
|
||||
{
|
||||
int simplexStartA = simplexCounts.GetSimplexStartAndSize(contacts[constraintIndex].bodyA, out int simplexSizeA);
|
||||
int simplexStartB = simplexCounts.GetSimplexStartAndSize(contacts[constraintIndex].bodyB, out int simplexSizeB);
|
||||
if (index < simplexSizeA)
|
||||
return simplices[simplexStartA + index];
|
||||
else
|
||||
return simplices[simplexStartB + index - simplexSizeA];
|
||||
}
|
||||
|
||||
public void WriteSortedConstraint(int constraintIndex, int sortedIndex)
|
||||
{
|
||||
sortedContacts[sortedIndex] = contacts[constraintIndex];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fec3d78324058457cb723a60fb4858a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct FluidInteractionProvider : IConstraintProvider
|
||||
{
|
||||
public NativeArray<FluidInteraction> interactions;
|
||||
public NativeArray<FluidInteraction> sortedInteractions;
|
||||
|
||||
public int GetConstraintCount()
|
||||
{
|
||||
return interactions.Length;
|
||||
}
|
||||
|
||||
public int GetParticleCount(int constraintIndex)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
public int GetParticle(int constraintIndex, int index)
|
||||
{
|
||||
return interactions[constraintIndex].GetParticle(index);
|
||||
}
|
||||
|
||||
public void WriteSortedConstraint(int constraintIndex, int sortedIndex)
|
||||
{
|
||||
sortedInteractions[sortedIndex] = interactions[constraintIndex];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 66b0d6e3db3c14b929541a23187c393b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,10 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
namespace Obi
|
||||
{
|
||||
public interface IConstraint
|
||||
{
|
||||
int GetParticleCount();
|
||||
int GetParticle(int index);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49760f68f387c45b8b1e5482a6d0dabe
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
namespace Obi
|
||||
{
|
||||
public interface IConstraintProvider
|
||||
{
|
||||
int GetConstraintCount();
|
||||
int GetParticleCount(int constraintIndex);
|
||||
int GetParticle(int constraintIndex, int index);
|
||||
void WriteSortedConstraint(int constraintIndex, int sortedIndex);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f9cbc3295db8411ba4ac8c9a75291ec
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct FluidInteraction : IConstraint
|
||||
{
|
||||
public float4 gradient;
|
||||
public float avgKernel;
|
||||
public float avgGradient;
|
||||
public int particleA;
|
||||
public int particleB;
|
||||
|
||||
public int GetParticleCount() { return 2; }
|
||||
public int GetParticle(int index) { return index == 0 ? particleA : particleB; }
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98e6b7036ee4a4806b75d155a753b769
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,76 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct GridHash
|
||||
{
|
||||
public readonly static int3[] cellOffsets3D =
|
||||
{
|
||||
new int3(1,0,0),
|
||||
new int3(0,1,0),
|
||||
new int3(1,1,0),
|
||||
new int3(0,0,1),
|
||||
new int3(1,0,1),
|
||||
new int3(0,1,1),
|
||||
new int3(1,1,1),
|
||||
new int3(-1,1,0),
|
||||
new int3(-1,-1,1),
|
||||
new int3(0,-1,1),
|
||||
new int3(1,-1,1),
|
||||
new int3(-1,0,1),
|
||||
new int3(-1,1,1)
|
||||
};
|
||||
|
||||
public readonly static int3[] cellOffsets =
|
||||
{
|
||||
new int3(0, 0, 0),
|
||||
new int3(-1, 0, 0),
|
||||
new int3(0, -1, 0),
|
||||
new int3(0, 0, -1),
|
||||
new int3(1, 0, 0),
|
||||
new int3(0, 1, 0),
|
||||
new int3(0, 0, 1)
|
||||
};
|
||||
|
||||
public readonly static int2[] cell2DOffsets =
|
||||
{
|
||||
new int2(0, 0),
|
||||
new int2(-1, 0),
|
||||
new int2(0, -1),
|
||||
new int2(1, 0),
|
||||
new int2(0, 1),
|
||||
};
|
||||
|
||||
public static int3 Quantize(float3 v, float cellSize)
|
||||
{
|
||||
return new int3(math.floor(v / cellSize));
|
||||
}
|
||||
|
||||
public static int2 Quantize(float2 v, float cellSize)
|
||||
{
|
||||
return new int2(math.floor(v / cellSize));
|
||||
}
|
||||
|
||||
public static int Hash(in int4 cellIndex, int maxCells)
|
||||
{
|
||||
const int p1 = 73856093;
|
||||
const int p2 = 19349663;
|
||||
const int p3 = 83492791;
|
||||
const int p4 = 10380569;
|
||||
return math.abs(p1 * cellIndex.x ^ p2 * cellIndex.y ^ p3 * cellIndex.z ^ p4 * cellIndex.w) % maxCells;
|
||||
}
|
||||
|
||||
public static int Hash(in int3 cellIndex, int maxCells)
|
||||
{
|
||||
const int p1 = 73856093;
|
||||
const int p2 = 19349663;
|
||||
const int p3 = 83492791;
|
||||
return ((p1 * cellIndex.x ^ p2 * cellIndex.y ^ p3 * cellIndex.z) & 0x7fffffff) % maxCells;
|
||||
|
||||
/*var index = cellIndex - new int3(-32, -32, -32);
|
||||
return index.x + index.y * 64 + index.z * 64 * 64;*/
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87666fac08214420abefb36a9166df43
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,282 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Mathematics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
|
||||
/**
|
||||
* MultilevelGrid is the most used spatial partitioning structure in Obi. It is:
|
||||
*
|
||||
* - Unbounded: defines no limits on the size of location of space partitioned.
|
||||
* - Sparse: only allocates memory where space has interesting features to track.
|
||||
* - Multilevel: can store several levels of spatial subdivision, from very fine to very large.
|
||||
* - Implicit: the hierarchical relationship between cells is not stored in memory,
|
||||
* but implicitly derived from the structure itself.
|
||||
*
|
||||
* These characteristics make it extremely flexible, memory efficient, and fast.
|
||||
* Its implementation is also fairly simple and concise.
|
||||
*/
|
||||
public unsafe struct NativeMultilevelGrid<T> : IDisposable where T : unmanaged, IEquatable<T>
|
||||
{
|
||||
|
||||
public const float minSize = 0.01f; // minimum cell size is 1 centimeter, enough for very small particles.
|
||||
public const int minLevel = -6; // grid level for minSize.
|
||||
public const int maxLevel = 17;
|
||||
|
||||
/**
|
||||
* A cell in the multilevel grid. Coords are 4-dimensional, the 4th component is the grid level.
|
||||
*/
|
||||
public struct Cell<K> where K : unmanaged, IEquatable<K>
|
||||
{
|
||||
int4 coords;
|
||||
UnsafeList<K> contents;
|
||||
|
||||
public Cell(int4 coords)
|
||||
{
|
||||
this.coords = coords;
|
||||
contents = new UnsafeList<K>(4,Allocator.Persistent);
|
||||
}
|
||||
|
||||
public int4 Coords
|
||||
{
|
||||
get { return coords; }
|
||||
}
|
||||
|
||||
public int Length
|
||||
{
|
||||
get { return contents.Length; }
|
||||
}
|
||||
|
||||
public void* ContentsPointer
|
||||
{
|
||||
get { return contents.Ptr; }
|
||||
}
|
||||
|
||||
public K this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return contents.ElementAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(K entity)
|
||||
{
|
||||
contents.Add(entity);
|
||||
}
|
||||
|
||||
public bool Remove(K entity)
|
||||
{
|
||||
int index = contents.IndexOf(entity);
|
||||
if (index >= 0)
|
||||
{
|
||||
contents.RemoveAtSwapBack(index);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
contents.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public NativeParallelHashMap<int4, int> grid;
|
||||
public NativeList<Cell<T>> usedCells;
|
||||
public NativeParallelHashMap<int, int> populatedLevels;
|
||||
|
||||
public NativeMultilevelGrid(int capacity, Allocator label)
|
||||
{
|
||||
grid = new NativeParallelHashMap<int4, int>(capacity, label);
|
||||
usedCells = new NativeList<Cell<T>>(label);
|
||||
populatedLevels = new NativeParallelHashMap<int, int>(10, label);
|
||||
}
|
||||
|
||||
public int CellCount
|
||||
{
|
||||
get { return usedCells.Length; }
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
for (int i = 0; i < usedCells.Length; ++i)
|
||||
usedCells[i].Dispose();
|
||||
|
||||
grid.Clear();
|
||||
usedCells.Clear();
|
||||
populatedLevels.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < usedCells.Length; ++i)
|
||||
usedCells[i].Dispose();
|
||||
|
||||
grid.Dispose();
|
||||
usedCells.Dispose();
|
||||
populatedLevels.Dispose();
|
||||
}
|
||||
|
||||
public int GetOrCreateCell(int4 cellCoords)
|
||||
{
|
||||
int cellIndex;
|
||||
if (grid.TryGetValue(cellCoords, out cellIndex))
|
||||
{
|
||||
return cellIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
grid.TryAdd(cellCoords, usedCells.Length);
|
||||
usedCells.Add(new Cell<T>(cellCoords));
|
||||
|
||||
IncreaseLevelPopulation(cellCoords.w);
|
||||
|
||||
return usedCells.Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetCellIndex(int4 cellCoords, out int cellIndex)
|
||||
{
|
||||
return grid.TryGetValue(cellCoords, out cellIndex);
|
||||
}
|
||||
|
||||
public void RemoveEmpty()
|
||||
{
|
||||
// remove empty cells from the used cells list and the grid:
|
||||
for (int i = usedCells.Length - 1; i >= 0 ; --i)
|
||||
{
|
||||
if (usedCells[i].Length == 0)
|
||||
{
|
||||
DecreaseLevelPopulation(usedCells[i].Coords.w);
|
||||
grid.Remove(usedCells[i].Coords);
|
||||
usedCells[i].Dispose();
|
||||
usedCells.RemoveAtSwapBack(i);
|
||||
}
|
||||
}
|
||||
|
||||
// update grid indices:
|
||||
for (int i = 0; i < usedCells.Length; ++i)
|
||||
{
|
||||
grid.Remove(usedCells[i].Coords);
|
||||
grid.TryAdd(usedCells[i].Coords, i);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GridLevelForSize(float size)
|
||||
{
|
||||
// the magic number is 1/log(2), used because log_a(x) = log_b(x) / log_b(a)
|
||||
// level is clamped between MIN_LEVEL and MAX_LEVEL, then remapped to (0, MAX_LEVEL - MIN_LEVEL)
|
||||
// this allows us to avoid InterlockedMax issues on GPU, since it doesn't work on negative numbers on some APIs.
|
||||
return math.clamp((int)math.ceil(math.log(size) * 1.44269504089f), minLevel, maxLevel) - minLevel;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static float CellSizeOfLevel(int level)
|
||||
{
|
||||
return math.exp2(level + minLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a cell coordinate, returns the coordinates of the cell containing it in a superior level.
|
||||
*/
|
||||
public static int4 GetParentCellCoords(int4 cellCoords, int level)
|
||||
{
|
||||
float decimation = math.exp2(level - cellCoords[3]);
|
||||
int4 cell = (int4)math.floor((float4)cellCoords / decimation);
|
||||
cell[3] = level;
|
||||
return cell;
|
||||
}
|
||||
|
||||
public void RemoveFromCells(BurstCellSpan span, T content)
|
||||
{
|
||||
for (int x = span.min[0]; x <= span.max[0]; ++x)
|
||||
for (int y = span.min[1]; y <= span.max[1]; ++y)
|
||||
for (int z = span.min[2]; z <= span.max[2]; ++z)
|
||||
{
|
||||
int cellIndex;
|
||||
if (TryGetCellIndex(new int4(x, y, z, span.level), out cellIndex))
|
||||
{
|
||||
var oldCell = usedCells[cellIndex];
|
||||
oldCell.Remove(content);
|
||||
usedCells[cellIndex] = oldCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AddToCells(BurstCellSpan span, T content)
|
||||
{
|
||||
for (int x = span.min[0]; x <= span.max[0]; ++x)
|
||||
for (int y = span.min[1]; y <= span.max[1]; ++y)
|
||||
for (int z = span.min[2]; z <= span.max[2]; ++z)
|
||||
{
|
||||
int cellIndex = GetOrCreateCell(new int4(x, y, z, span.level));
|
||||
|
||||
var newCell = usedCells[cellIndex];
|
||||
newCell.Add(content);
|
||||
usedCells[cellIndex] = newCell;
|
||||
}
|
||||
}
|
||||
|
||||
public static void GetCellCoordsForBoundsAtLevel(NativeList<int4> coords, BurstAabb bounds, int level, int maxSize = 10)
|
||||
{
|
||||
coords.Clear();
|
||||
float cellSize = CellSizeOfLevel(level);
|
||||
|
||||
int3 minCell = GridHash.Quantize(bounds.min.xyz, cellSize);
|
||||
int3 maxCell = GridHash.Quantize(bounds.max.xyz, cellSize);
|
||||
maxCell = minCell + math.min(maxCell - minCell, new int3(maxSize));
|
||||
|
||||
int3 size = maxCell - minCell + new int3(1);
|
||||
|
||||
coords.Capacity = size.x * size.y * size.z;
|
||||
|
||||
// TODO: return some sort of iterator trough the cells, not a native array.
|
||||
for (int x = minCell[0]; x <= maxCell[0]; ++x)
|
||||
{
|
||||
for (int y = minCell[1]; y <= maxCell[1]; ++y)
|
||||
{
|
||||
for (int z = minCell[2]; z <= maxCell[2]; ++z)
|
||||
{
|
||||
coords.Add(new int4(x, y, z, level));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IncreaseLevelPopulation(int level)
|
||||
{
|
||||
|
||||
int population = 0;
|
||||
if (populatedLevels.TryGetValue(level, out population))
|
||||
{
|
||||
populatedLevels.Remove(level);
|
||||
}
|
||||
|
||||
populatedLevels.TryAdd(level, population + 1);
|
||||
}
|
||||
|
||||
private void DecreaseLevelPopulation(int level)
|
||||
{
|
||||
|
||||
int population = 0;
|
||||
if (populatedLevels.TryGetValue(level, out population))
|
||||
{
|
||||
population--;
|
||||
populatedLevels.Remove(level);
|
||||
|
||||
if (population > 0)
|
||||
{
|
||||
populatedLevels.TryAdd(level, population);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2745cdd0ade4f4ac0a7ca952c1ad5dd8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,473 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using System;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
using Unity.Mathematics;
|
||||
using Unity.Burst;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct MovingEntity
|
||||
{
|
||||
public int4 oldCellCoord;
|
||||
public int4 newCellCoord;
|
||||
public int entity;
|
||||
}
|
||||
|
||||
public class ParticleGrid : IDisposable
|
||||
{
|
||||
public NativeMultilevelGrid<int> grid;
|
||||
public NativeQueue<BurstContact> particleContactQueue;
|
||||
public NativeQueue<FluidInteraction> fluidInteractionQueue;
|
||||
|
||||
[BurstCompile]
|
||||
struct UpdateGrid : IJob
|
||||
{
|
||||
public NativeMultilevelGrid<int> grid;
|
||||
[ReadOnly] public NativeArray<BurstAabb> simplexBounds;
|
||||
public NativeArray<int4> cellCoords;
|
||||
|
||||
[ReadOnly] public Oni.SolverParameters parameters;
|
||||
[ReadOnly] public int simplexCount;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
grid.Clear();
|
||||
|
||||
for (int i = 0; i < simplexCount; ++i)
|
||||
{
|
||||
int level = NativeMultilevelGrid<int>.GridLevelForSize(simplexBounds[i].MaxAxisLength());
|
||||
float cellSize = NativeMultilevelGrid<int>.CellSizeOfLevel(level);
|
||||
|
||||
// get new cell coordinate:
|
||||
int4 newCellCoord = new int4(GridHash.Quantize(simplexBounds[i].center.xyz, cellSize), level);
|
||||
|
||||
// if the solver is 2D, project to the z = 0 cell.
|
||||
if (parameters.mode == Oni.SolverParameters.Mode.Mode2D) newCellCoord[2] = 0;
|
||||
|
||||
cellCoords[i] = newCellCoord;
|
||||
|
||||
// add to new cell:
|
||||
int cellIndex = grid.GetOrCreateCell(cellCoords[i]);
|
||||
var newCell = grid.usedCells[cellIndex];
|
||||
newCell.Add(i);
|
||||
grid.usedCells[cellIndex] = newCell;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
public struct GenerateParticleParticleContactsJob : IJobParallelFor
|
||||
{
|
||||
[ReadOnly] public NativeMultilevelGrid<int> grid;
|
||||
|
||||
[DeallocateOnJobCompletion]
|
||||
[ReadOnly] public NativeArray<int> gridLevels;
|
||||
|
||||
[ReadOnly] public NativeArray<float4> positions;
|
||||
[ReadOnly] public NativeArray<quaternion> orientations;
|
||||
[ReadOnly] public NativeArray<float4> restPositions;
|
||||
[ReadOnly] public NativeArray<quaternion> restOrientations;
|
||||
[ReadOnly] public NativeArray<float4> velocities;
|
||||
[ReadOnly] public NativeArray<float> invMasses;
|
||||
[ReadOnly] public NativeArray<float4> radii;
|
||||
[ReadOnly] public NativeArray<float4> normals;
|
||||
[ReadOnly] public NativeArray<float4> fluidMaterials;
|
||||
[ReadOnly] public NativeArray<int> phases;
|
||||
[ReadOnly] public NativeArray<int> filters;
|
||||
|
||||
// simplex arrays:
|
||||
[ReadOnly] public NativeArray<int> simplices;
|
||||
[ReadOnly] public SimplexCounts simplexCounts;
|
||||
|
||||
[ReadOnly] public NativeArray<int> particleMaterialIndices;
|
||||
[ReadOnly] public NativeArray<BurstCollisionMaterial> collisionMaterials;
|
||||
|
||||
[WriteOnly]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeQueue<BurstContact>.ParallelWriter contactsQueue;
|
||||
|
||||
[WriteOnly]
|
||||
[NativeDisableParallelForRestriction]
|
||||
public NativeQueue<FluidInteraction>.ParallelWriter fluidInteractionsQueue;
|
||||
|
||||
[ReadOnly] public float dt;
|
||||
[ReadOnly] public float collisionMargin;
|
||||
[ReadOnly] public int optimizationIterations;
|
||||
[ReadOnly] public float optimizationTolerance;
|
||||
|
||||
public void Execute(int i)
|
||||
{
|
||||
BurstSimplex simplexShape = new BurstSimplex()
|
||||
{
|
||||
positions = restPositions,
|
||||
radii = radii,
|
||||
simplices = simplices,
|
||||
};
|
||||
|
||||
// Looks for close particles in the same cell:
|
||||
IntraCellSearch(i, ref simplexShape);
|
||||
|
||||
// Looks for close particles in neighboring cells, in the same level or higher levels.
|
||||
IntraLevelSearch(i, ref simplexShape);
|
||||
}
|
||||
|
||||
private void IntraCellSearch(int cellIndex, ref BurstSimplex simplexShape)
|
||||
{
|
||||
int cellLength = grid.usedCells[cellIndex].Length;
|
||||
|
||||
for (int p = 0; p < cellLength; ++p)
|
||||
{
|
||||
for (int n = p + 1; n < cellLength; ++n)
|
||||
{
|
||||
InteractionTest(grid.usedCells[cellIndex][p], grid.usedCells[cellIndex][n], ref simplexShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InterCellSearch(int cellIndex, int neighborCellIndex, ref BurstSimplex simplexShape)
|
||||
{
|
||||
int cellLength = grid.usedCells[cellIndex].Length;
|
||||
int neighborCellLength = grid.usedCells[neighborCellIndex].Length;
|
||||
|
||||
for (int p = 0; p < cellLength; ++p)
|
||||
{
|
||||
for (int n = 0; n < neighborCellLength; ++n)
|
||||
{
|
||||
InteractionTest(grid.usedCells[cellIndex][p], grid.usedCells[neighborCellIndex][n], ref simplexShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void IntraLevelSearch(int cellIndex, ref BurstSimplex simplexShape)
|
||||
{
|
||||
int4 cellCoords = grid.usedCells[cellIndex].Coords;
|
||||
|
||||
// neighboring cells in the current level:
|
||||
for (int i = 0; i < 13; ++i)
|
||||
{
|
||||
int4 neighborCellCoords = new int4(cellCoords.xyz + GridHash.cellOffsets3D[i], cellCoords.w);
|
||||
|
||||
int neighborCellIndex;
|
||||
if (grid.TryGetCellIndex(neighborCellCoords, out neighborCellIndex))
|
||||
{
|
||||
InterCellSearch(cellIndex, neighborCellIndex, ref simplexShape);
|
||||
}
|
||||
}
|
||||
|
||||
// neighboring cells in levels above the current one:
|
||||
int levelIndex = gridLevels.IndexOf<int, int>(cellCoords.w);
|
||||
if (levelIndex >= 0)
|
||||
{
|
||||
levelIndex++;
|
||||
for (; levelIndex < gridLevels.Length; ++levelIndex)
|
||||
{
|
||||
int level = gridLevels[levelIndex];
|
||||
|
||||
// calculate index of parent cell in parent level:
|
||||
int4 parentCellCoords = NativeMultilevelGrid<int>.GetParentCellCoords(cellCoords, level);
|
||||
|
||||
// search in all neighbouring cells:
|
||||
for (int x = -1; x <= 1; ++x)
|
||||
for (int y = -1; y <= 1; ++y)
|
||||
for (int z = -1; z <= 1; ++z)
|
||||
{
|
||||
int4 neighborCellCoords = parentCellCoords + new int4(x, y, z, 0);
|
||||
|
||||
int neighborCellIndex;
|
||||
if (grid.TryGetCellIndex(neighborCellCoords, out neighborCellIndex))
|
||||
{
|
||||
InterCellSearch(cellIndex, neighborCellIndex, ref simplexShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int GetSimplexGroup(int simplexStart, int simplexSize, out ObiUtils.ParticleFlags flags, out int category, out int mask, ref bool restPositionsEnabled)
|
||||
{
|
||||
flags = 0;
|
||||
int group = 0;
|
||||
category = 0;
|
||||
mask = 0;
|
||||
for (int j = 0; j < simplexSize; ++j)
|
||||
{
|
||||
int particleIndex = simplices[simplexStart + j];
|
||||
group = math.max(group, ObiUtils.GetGroupFromPhase(phases[particleIndex]));
|
||||
flags |= ObiUtils.GetFlagsFromPhase(phases[particleIndex]);
|
||||
category |= filters[particleIndex] & ObiUtils.FilterCategoryBitmask;
|
||||
mask |= (filters[particleIndex] & ObiUtils.FilterMaskBitmask) >> 16;
|
||||
restPositionsEnabled |= restPositions[particleIndex].w > 0.5f;
|
||||
}
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
private void InteractionTest(int A, int B, ref BurstSimplex simplexShape)
|
||||
{
|
||||
// get the start index and size of each simplex:
|
||||
int simplexStartA = simplexCounts.GetSimplexStartAndSize(A, out int simplexSizeA);
|
||||
int simplexStartB = simplexCounts.GetSimplexStartAndSize(B, out int simplexSizeB);
|
||||
|
||||
// immediately reject simplex pairs that share particles:
|
||||
for (int a = 0; a < simplexSizeA; ++a)
|
||||
for (int b = 0; b < simplexSizeB; ++b)
|
||||
if (simplices[simplexStartA + a] == simplices[simplexStartB + b])
|
||||
return;
|
||||
|
||||
// get group for each simplex:
|
||||
bool restPositionsEnabled = false;
|
||||
int groupA = GetSimplexGroup(simplexStartA, simplexSizeA, out ObiUtils.ParticleFlags flagsA, out int categoryA, out int maskA, ref restPositionsEnabled);
|
||||
int groupB = GetSimplexGroup(simplexStartB, simplexSizeB, out ObiUtils.ParticleFlags flagsB, out int categoryB, out int maskB, ref restPositionsEnabled);
|
||||
|
||||
// if all particles are in the same group:
|
||||
if (groupA == groupB)
|
||||
{
|
||||
// if none are self-colliding, reject the pair.
|
||||
if ((flagsA & flagsB & ObiUtils.ParticleFlags.SelfCollide) == 0)
|
||||
return;
|
||||
}
|
||||
// category-based filtering:
|
||||
else if ((maskA & categoryB) == 0 || (maskB & categoryA) == 0)
|
||||
return;
|
||||
|
||||
// if all simplices are fluid, check their smoothing radii:
|
||||
if ((flagsA & ObiUtils.ParticleFlags.Fluid) != 0 && (flagsB & ObiUtils.ParticleFlags.Fluid) != 0)
|
||||
{
|
||||
// for fluid we only consider the first particle in each simplex.
|
||||
int particleA = simplices[simplexStartA];
|
||||
int particleB = simplices[simplexStartB];
|
||||
|
||||
// Calculate particle center distance:
|
||||
float d2 = math.lengthsq(positions[particleA].xyz - positions[particleB].xyz);
|
||||
|
||||
float fluidDistance = math.max(fluidMaterials[particleA].x, fluidMaterials[particleB].x) + collisionMargin;
|
||||
if (d2 <= fluidDistance * fluidDistance)
|
||||
{
|
||||
fluidInteractionsQueue.Enqueue(new FluidInteraction { particleA = particleA, particleB = particleB });
|
||||
}
|
||||
}
|
||||
else // at least one solid particle is present:
|
||||
{
|
||||
// swap simplices so that B is always the one-sided one.
|
||||
if ((flagsA & ObiUtils.ParticleFlags.OneSided) != 0 && categoryA < categoryB)
|
||||
{
|
||||
ObiUtils.Swap(ref A, ref B);
|
||||
ObiUtils.Swap(ref simplexStartA, ref simplexStartB);
|
||||
ObiUtils.Swap(ref simplexSizeA, ref simplexSizeB);
|
||||
ObiUtils.Swap(ref flagsA, ref flagsB);
|
||||
ObiUtils.Swap(ref groupA, ref groupB);
|
||||
}
|
||||
|
||||
float4 simplexBary = BurstMath.BarycenterForSimplexOfSize(simplexSizeA);
|
||||
float4 simplexPoint;
|
||||
|
||||
simplexShape.simplexStart = simplexStartB;
|
||||
simplexShape.simplexSize = simplexSizeB;
|
||||
simplexShape.positions = restPositions;
|
||||
simplexShape.CacheData();
|
||||
|
||||
float simplexRadiusA = 0, simplexRadiusB = 0;
|
||||
|
||||
// skip the contact if there's self-intersection at rest:
|
||||
if (groupA == groupB && restPositionsEnabled)
|
||||
{
|
||||
var restPoint = BurstLocalOptimization.Optimize<BurstSimplex>(ref simplexShape, restPositions, restOrientations, radii,
|
||||
simplices, simplexStartA, simplexSizeA, ref simplexBary, out simplexPoint, 4, 0);
|
||||
|
||||
for (int j = 0; j < simplexSizeA; ++j)
|
||||
simplexRadiusA += radii[simplices[simplexStartA + j]].x * simplexBary[j];
|
||||
|
||||
for (int j = 0; j < simplexSizeB; ++j)
|
||||
simplexRadiusB += radii[simplices[simplexStartB + j]].x * restPoint.bary[j];
|
||||
|
||||
// compare distance along contact normal with radius.
|
||||
if (math.dot(simplexPoint - restPoint.point, restPoint.normal) < simplexRadiusA + simplexRadiusB)
|
||||
return;
|
||||
}
|
||||
|
||||
simplexBary = BurstMath.BarycenterForSimplexOfSize(simplexSizeA);
|
||||
simplexShape.positions = positions;
|
||||
simplexShape.CacheData();
|
||||
|
||||
var surfacePoint = BurstLocalOptimization.Optimize<BurstSimplex>(ref simplexShape, positions, orientations, radii,
|
||||
simplices, simplexStartA, simplexSizeA, ref simplexBary, out simplexPoint, optimizationIterations, optimizationTolerance);
|
||||
|
||||
simplexRadiusA = 0; simplexRadiusB = 0;
|
||||
float4 velocityA = float4.zero, velocityB = float4.zero, normalA = float4.zero, normalB = float4.zero;
|
||||
float invMassA = 0, invMassB = 0;
|
||||
|
||||
for (int j = 0; j < simplexSizeA; ++j)
|
||||
{
|
||||
int particleIndex = simplices[simplexStartA + j];
|
||||
simplexRadiusA += radii[particleIndex].x * simplexBary[j];
|
||||
velocityA += velocities[particleIndex] * simplexBary[j];
|
||||
normalA += (normals[particleIndex].w < 0 ? new float4(math.rotate(orientations[particleIndex],normals[particleIndex].xyz), normals[particleIndex].w) : normals[particleIndex]) * simplexBary[j];
|
||||
invMassA += invMasses[particleIndex] * simplexBary[j];
|
||||
}
|
||||
|
||||
for (int j = 0; j < simplexSizeB; ++j)
|
||||
{
|
||||
int particleIndex = simplices[simplexStartB + j];
|
||||
simplexRadiusB += radii[particleIndex].x * surfacePoint.bary[j];
|
||||
velocityB += velocities[particleIndex] * surfacePoint.bary[j];
|
||||
normalB += (normals[particleIndex].w < 0 ? new float4(math.rotate(orientations[particleIndex], normals[particleIndex].xyz), normals[particleIndex].w) : normals[particleIndex]) * surfacePoint.bary[j];
|
||||
invMassB += invMasses[particleIndex] * simplexBary[j];
|
||||
}
|
||||
|
||||
// no contact between fixed simplices:
|
||||
//if (!(invMassA > 0 || invMassB > 0))
|
||||
// return;
|
||||
|
||||
float dAB = math.dot(simplexPoint - surfacePoint.point, surfacePoint.normal);
|
||||
float vel = math.dot(velocityA - velocityB, surfacePoint.normal);
|
||||
|
||||
// check if the projected velocity along the contact normal will get us within collision distance.
|
||||
if (vel * dt + dAB <= simplexRadiusA + simplexRadiusB + collisionMargin)
|
||||
{
|
||||
// adapt collision normal for one-sided simplices:
|
||||
if ((flagsB & ObiUtils.ParticleFlags.OneSided) != 0 && categoryA < categoryB)
|
||||
BurstMath.OneSidedNormal(normalB, ref surfacePoint.normal);
|
||||
|
||||
// during inter-collision, if either particle contains SDF data and they overlap:
|
||||
if (groupA != groupB && (normalB.w < 0 || normalA.w < 0) && dAB * 1.05f <= simplexRadiusA + simplexRadiusB)
|
||||
{
|
||||
// as normal, pick SDF gradient belonging to least penetration distance:
|
||||
float4 nij = normalB;
|
||||
if (normalB.w >= 0 || (normalA.w < 0 && normalB.w < normalA.w))
|
||||
nij = new float4(-normalA.xyz, normalA.w);
|
||||
|
||||
// for boundary particles, use one sided sphere normal:
|
||||
if (math.abs(nij.w) <= math.max(simplexRadiusA, simplexRadiusB) * 1.5f)
|
||||
BurstMath.OneSidedNormal(nij, ref surfacePoint.normal);
|
||||
else
|
||||
surfacePoint.normal = nij;
|
||||
}
|
||||
|
||||
surfacePoint.normal.w = 0;
|
||||
contactsQueue.Enqueue(new BurstContact
|
||||
{
|
||||
bodyA = A,
|
||||
bodyB = B,
|
||||
pointA = simplexBary,
|
||||
pointB = surfacePoint.bary,
|
||||
normal = surfacePoint.normal
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ParticleGrid()
|
||||
{
|
||||
this.grid = new NativeMultilevelGrid<int>(1000, Allocator.Persistent);
|
||||
this.particleContactQueue = new NativeQueue<BurstContact>(Allocator.Persistent);
|
||||
this.fluidInteractionQueue = new NativeQueue<FluidInteraction>(Allocator.Persistent);
|
||||
}
|
||||
|
||||
public void Update(BurstSolverImpl solver, JobHandle inputDeps)
|
||||
{
|
||||
var updateGrid = new UpdateGrid
|
||||
{
|
||||
grid = grid,
|
||||
simplexBounds = solver.simplexBounds,
|
||||
simplexCount = solver.simplexCounts.simplexCount,
|
||||
cellCoords = solver.cellCoords,
|
||||
parameters = solver.abstraction.parameters
|
||||
};
|
||||
updateGrid.Schedule(inputDeps).Complete();
|
||||
}
|
||||
|
||||
public JobHandle GenerateContacts(BurstSolverImpl solver, float deltaTime)
|
||||
{
|
||||
|
||||
var generateParticleContactsJob = new GenerateParticleParticleContactsJob
|
||||
{
|
||||
grid = grid,
|
||||
gridLevels = grid.populatedLevels.GetKeyArray(Allocator.TempJob),
|
||||
|
||||
positions = solver.positions,
|
||||
orientations = solver.orientations,
|
||||
restPositions = solver.restPositions,
|
||||
restOrientations = solver.restOrientations,
|
||||
velocities = solver.velocities,
|
||||
invMasses = solver.invMasses,
|
||||
radii = solver.principalRadii,
|
||||
normals = solver.normals,
|
||||
fluidMaterials = solver.fluidMaterials,
|
||||
phases = solver.phases,
|
||||
filters = solver.filters,
|
||||
|
||||
simplices = solver.simplices,
|
||||
simplexCounts = solver.simplexCounts,
|
||||
|
||||
particleMaterialIndices = solver.abstraction.collisionMaterials.AsNativeArray<int>(),
|
||||
collisionMaterials = ObiColliderWorld.GetInstance().collisionMaterials.AsNativeArray<BurstCollisionMaterial>(),
|
||||
|
||||
contactsQueue = particleContactQueue.AsParallelWriter(),
|
||||
fluidInteractionsQueue = fluidInteractionQueue.AsParallelWriter(),
|
||||
dt = deltaTime,
|
||||
collisionMargin = solver.abstraction.parameters.collisionMargin,
|
||||
optimizationIterations = solver.abstraction.parameters.surfaceCollisionIterations,
|
||||
optimizationTolerance = solver.abstraction.parameters.surfaceCollisionTolerance,
|
||||
};
|
||||
|
||||
return generateParticleContactsJob.Schedule(grid.CellCount, 1);
|
||||
}
|
||||
|
||||
public JobHandle SpatialQuery(BurstSolverImpl solver,
|
||||
NativeArray<BurstQueryShape> shapes,
|
||||
NativeArray<BurstAffineTransform> transforms,
|
||||
NativeQueue<BurstQueryResult> results)
|
||||
{
|
||||
var job = new SpatialQueryJob
|
||||
{
|
||||
grid = grid,
|
||||
|
||||
positions = solver.abstraction.prevPositions.AsNativeArray<float4>(),
|
||||
orientations = solver.abstraction.prevOrientations.AsNativeArray<quaternion>(),
|
||||
radii = solver.abstraction.principalRadii.AsNativeArray<float4>(),
|
||||
filters = solver.abstraction.filters.AsNativeArray<int>(),
|
||||
|
||||
simplices = solver.simplices,
|
||||
simplexCounts = solver.simplexCounts,
|
||||
|
||||
shapes = shapes,
|
||||
transforms = transforms,
|
||||
|
||||
results = results.AsParallelWriter(),
|
||||
worldToSolver = solver.worldToSolver,
|
||||
parameters = solver.abstraction.parameters
|
||||
};
|
||||
|
||||
return job.Schedule(shapes.Length, 4);
|
||||
}
|
||||
|
||||
public void GetCells(ObiNativeAabbList cells)
|
||||
{
|
||||
if (cells.count == grid.usedCells.Length)
|
||||
{
|
||||
for (int i = 0; i < grid.usedCells.Length; ++i)
|
||||
{
|
||||
var cell = grid.usedCells[i];
|
||||
float size = NativeMultilevelGrid<int>.CellSizeOfLevel(cell.Coords.w);
|
||||
|
||||
float4 min = (float4)cell.Coords * size;
|
||||
min[3] = 0;
|
||||
|
||||
cells[i] = new Aabb(min, min + new float4(size, size, size, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
grid.Dispose();
|
||||
particleContactQueue.Dispose();
|
||||
fluidInteractionQueue.Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 581337416adb545a388c356940ae38be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c5f4522f2a394ac0a5017b7de9a31d5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,163 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Mathematics;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstContact : IConstraint, System.IComparable<BurstContact>
|
||||
{
|
||||
public float4 pointA; // point A, expressed as simplex barycentric coords for simplices, as a solver-space position for colliders.
|
||||
public float4 pointB; // point B, expressed as simplex barycentric coords for simplices, as a solver-space position for colliders.
|
||||
|
||||
public float4 normal; // contact normal on bodyB's surface.
|
||||
public float4 tangent; // contact tangent on bodyB's surface.
|
||||
|
||||
public float distance; // distance between bodyA's and bodyB's surface.
|
||||
|
||||
public float normalLambda;
|
||||
public float tangentLambda;
|
||||
public float bitangentLambda;
|
||||
public float stickLambda;
|
||||
public float rollingFrictionImpulse;
|
||||
|
||||
public int bodyA;
|
||||
public int bodyB;
|
||||
|
||||
public int GetParticleCount() { return 2; }
|
||||
public int GetParticle(int index) { return index == 0 ? bodyA : bodyB; }
|
||||
|
||||
public float4 bitangent => math.normalizesafe(new float4(math.cross(normal.xyz, tangent.xyz), 0));
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return bodyA + "," + bodyB;
|
||||
}
|
||||
|
||||
public int CompareTo(BurstContact other)
|
||||
{
|
||||
int first = bodyA.CompareTo(other.bodyA);
|
||||
if (first == 0)
|
||||
return bodyB.CompareTo(other.bodyB);
|
||||
return first;
|
||||
}
|
||||
|
||||
public void CalculateTangent(float4 relativeVelocity)
|
||||
{
|
||||
tangent = math.normalizesafe(relativeVelocity - math.dot(relativeVelocity, normal) * normal);
|
||||
}
|
||||
|
||||
public float SolveAdhesion(float normalMass, float4 posA, float4 posB, float stickDistance, float stickiness, float dt)
|
||||
{
|
||||
|
||||
if (normalMass <= 0 || stickDistance <= 0 || stickiness <= 0 || dt <= 0)
|
||||
return 0;
|
||||
|
||||
distance = math.dot(posA - posB, normal);
|
||||
|
||||
// calculate stickiness position correction:
|
||||
float constraint = stickiness * (1 - math.max(distance / stickDistance, 0)) * dt;
|
||||
|
||||
// calculate lambda multiplier:
|
||||
float dlambda = -constraint / normalMass;
|
||||
|
||||
// accumulate lambda:
|
||||
float newStickinessLambda = math.min(stickLambda + dlambda, 0);
|
||||
|
||||
// calculate lambda change and update accumulated lambda:
|
||||
float lambdaChange = newStickinessLambda - stickLambda;
|
||||
stickLambda = newStickinessLambda;
|
||||
|
||||
return lambdaChange;
|
||||
}
|
||||
|
||||
public float SolvePenetration(float normalMass, float4 posA, float4 posB, float maxDepenetrationDelta)
|
||||
{
|
||||
if (normalMass <= 0)
|
||||
return 0;
|
||||
|
||||
//project position delta to normal vector:
|
||||
distance = math.dot(posA - posB, normal);
|
||||
|
||||
// calculate max projection distance based on depenetration velocity:
|
||||
float maxProjection = math.max(-distance - maxDepenetrationDelta, 0);
|
||||
|
||||
// calculate lambda multiplier:
|
||||
float dlambda = -(distance + maxProjection) / normalMass;
|
||||
|
||||
// accumulate lambda:
|
||||
float newLambda = math.max(normalLambda + dlambda, 0);
|
||||
|
||||
// calculate lambda change and update accumulated lambda:
|
||||
float lambdaChange = newLambda - normalLambda;
|
||||
normalLambda = newLambda;
|
||||
|
||||
return lambdaChange;
|
||||
}
|
||||
|
||||
public float2 SolveFriction(float tangentMass, float bitangentMass, float4 relativeVelocity, float staticFriction, float dynamicFriction, float dt)
|
||||
{
|
||||
float2 lambdaChange = float2.zero;
|
||||
|
||||
if (tangentMass <= 0 || bitangentMass <= 0 ||
|
||||
(dynamicFriction <= 0 && staticFriction <= 0) || (normalLambda <= 0 && stickLambda <= 0))
|
||||
return lambdaChange;
|
||||
|
||||
// calculate delta projection on both friction axis:
|
||||
float tangentPosDelta = math.dot(relativeVelocity, tangent);
|
||||
float bitangentPosDelta = math.dot(relativeVelocity, bitangent);
|
||||
|
||||
// calculate friction pyramid limit:
|
||||
float dynamicFrictionCone = normalLambda / dt * dynamicFriction;
|
||||
float staticFrictionCone = normalLambda / dt * staticFriction;
|
||||
|
||||
// tangent impulse:
|
||||
float tangentLambdaDelta = -tangentPosDelta / tangentMass;
|
||||
float newTangentLambda = tangentLambda + tangentLambdaDelta;
|
||||
|
||||
if (math.abs(newTangentLambda) > staticFrictionCone)
|
||||
newTangentLambda = math.clamp(newTangentLambda, -dynamicFrictionCone, dynamicFrictionCone);
|
||||
|
||||
lambdaChange[0] = newTangentLambda - tangentLambda;
|
||||
tangentLambda = newTangentLambda;
|
||||
|
||||
// bitangent impulse:
|
||||
float bitangentLambdaDelta = -bitangentPosDelta / bitangentMass;
|
||||
float newBitangentLambda = bitangentLambda + bitangentLambdaDelta;
|
||||
|
||||
if (math.abs(newBitangentLambda) > staticFrictionCone)
|
||||
newBitangentLambda = math.clamp(newBitangentLambda, -dynamicFrictionCone, dynamicFrictionCone);
|
||||
|
||||
lambdaChange[1] = newBitangentLambda - bitangentLambda;
|
||||
bitangentLambda = newBitangentLambda;
|
||||
|
||||
return lambdaChange;
|
||||
}
|
||||
|
||||
|
||||
public float SolveRollingFriction(float4 angularVelocityA,
|
||||
float4 angularVelocityB,
|
||||
float rollingFriction,
|
||||
float invMassA,
|
||||
float invMassB,
|
||||
ref float4 rolling_axis)
|
||||
{
|
||||
float totalInvMass = invMassA + invMassB;
|
||||
if (totalInvMass <= 0)
|
||||
return 0;
|
||||
|
||||
rolling_axis = math.normalizesafe(angularVelocityA - angularVelocityB);
|
||||
|
||||
float vel1 = math.dot(angularVelocityA,rolling_axis);
|
||||
float vel2 = math.dot(angularVelocityB,rolling_axis);
|
||||
|
||||
float relativeVelocity = vel1 - vel2;
|
||||
|
||||
float maxImpulse = normalLambda * rollingFriction;
|
||||
float newRollingImpulse = math.clamp(rollingFrictionImpulse - relativeVelocity / totalInvMass, -maxImpulse, maxImpulse);
|
||||
float rolling_impulse_change = newRollingImpulse - rollingFrictionImpulse;
|
||||
rollingFrictionImpulse = newRollingImpulse;
|
||||
|
||||
return rolling_impulse_change;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70486b82a23044fccaf324d309757b8f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
#if (OBI_BURST && OBI_MATHEMATICS && OBI_COLLECTIONS)
|
||||
using Unity.Mathematics;
|
||||
using Unity.Collections;
|
||||
|
||||
namespace Obi
|
||||
{
|
||||
public struct BurstQueryResult
|
||||
{
|
||||
public float4 simplexBary; // point A, expressed as simplex barycentric coords for simplices, as a solver-space position for colliders.
|
||||
public float4 queryPoint; // point B, expressed as simplex barycentric coords for simplices, as a solver-space position for colliders.
|
||||
public float4 normal;
|
||||
public float distance;
|
||||
public float distanceAlongRay;
|
||||
public int simplexIndex;
|
||||
public int queryIndex;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a3a82674a4b3b432ebcddfc108bb44c7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user