You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

165 lines
5.3 KiB

using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.VFX;
namespace UnityEditor.VFX.Block
{
class CollisionSphere : CollisionShapeBase
{
public class InputProperties
{
[Tooltip("Sets the sphere with which particles can collide.")]
public TSphere sphere = TSphere.defaultValue;
}
public override IEnumerable<VFXNamedExpression> GetParameters(CollisionBase collisionBase, IEnumerable<VFXNamedExpression> collisionBaseParameters)
{
VFXExpression transform = null;
VFXExpression radius = null;
foreach (var param in collisionBaseParameters)
{
if (param.name.StartsWith("sphere"))
{
if (param.name == "sphere_transform")
transform = param.exp;
if (param.name == "sphere_radius")
radius = param.exp;
continue; //exclude all automatic sphere inputs
}
yield return param;
}
VFXExpression finalTransform;
//Integrate directly the radius into the common transform matrix
var radiusScale = VFXOperatorUtility.UniformScaleMatrix(radius);
finalTransform = new VFXExpressionTransformMatrix(transform, radiusScale);
var isZeroScaled = VFXOperatorUtility.IsTRSMatrixZeroScaled(finalTransform);
yield return new VFXNamedExpression(isZeroScaled, "isZeroScaled");
var isUniformScaled = VFXOperatorUtility.IsTRSMatrixUniformScaled(finalTransform);
yield return new VFXNamedExpression(isUniformScaled, "isUniformScale");
yield return new VFXNamedExpression(finalTransform, "fieldTransform");
yield return new VFXNamedExpression(new VFXExpressionInverseTRSMatrix(finalTransform), "invFieldTransform");
VFXExpression scale = new VFXExpressionExtractScaleFromMatrix(finalTransform);
yield return new VFXNamedExpression(VFXOperatorUtility.Reciprocal(scale), "invScale");
yield return new VFXNamedExpression(scale, "scale");
}
public override string GetSource(CollisionBase collisionBase)
{
var Source = string.Format("const bool kUseParticleRadius = {0};\n",
collisionBase.radiusMode == CollisionBase.RadiusMode.None ? "false" : "true");
Source += @"
if (isZeroScaled)
return;
// Scale the ellipsoid to account for particle radius. This is not exactly correct as solving the distance function of an ellipsoid
// d(x,y,z) = r does not define an ellipsoid for r != 0 but it's a good enough approximation. The error will be noticeable only for very squashed ellipoids at boundaries
float3 s = float3(1,1,1);
if (kUseParticleRadius)
{
s = (colliderSign * radius) * invScale + 1.0f;
fieldTransform = mul(fieldTransform, GetScaleMatrix44(s));
invFieldTransform = mul(GetScaleMatrix44(rcp(s)), invFieldTransform);
}
float3 tVel = mul(invFieldTransform, float4(velocity, 0.0f)).xyz;
float3 tPos = mul(invFieldTransform, float4(position, 1.0f)).xyz;
float3 dPos = tVel * deltaTime;
// unit sphere / ray intersection
float a = dot(dPos,dPos);
float b = dot(tPos,dPos);
float c = dot(tPos,tPos) - 1.0f;
float d = b*b - a*c;
tHit = -1.0f;
if (d >= 0) // Line intersection
{
d = sqrt(d);
float t0 = -(b + d);
float t1 = -(b - d);
tHit = t0 >= 0 ? t0 : t1;
}
if (tHit >= 0 && tHit < a) // Intersection with ray
{
hit = true;
tHit /= a;
tPos += tHit * dPos; // Point of intersection
}
else if (c * colliderSign < 0) // Inside volume
{
hit = true;
tHit = 0.0f;
if (isUniformScale) // Fast path for spheres
{
// Simply teleport on closest point on sphere
tPos *= rsqrt(c + 1);
}
else
{
#ifdef FAST_COLLISIONS
float3 u = invScale;
float3 n = normalize(tPos * u);
b = dot(tPos,n);
d = sqrt(b*b - c);
tPos -= (b - d) * n;
#else
// Find closest point on ellipsoid
// https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
float3 z = abs(tPos) + VFX_EPSILON;
float3 r = scale * s;
float minScale = Min3(r.x, r.y, r.z);
r *= rcp(minScale);
r = r * r;
float3 m = r * z;
float t0 = -1;
if (r.y < r.x)
t0 += r.z < r.y ? z.z : z.y;
else
t0 += r.z < r.x ? z.z : z.x;
float t1 = colliderSign > 0 ? 0 : length(m) - 1;
// Find root using bisection with N iteration
const int ITERATION_COUNT = 16;
float t = 0;
int i = 0;
for (i = 0; i < ITERATION_COUNT; ++i)
{
t = (t0 + t1) * 0.5f;
float3 ratio = m * rcp(t + r);
float dr = dot(ratio,ratio) - 1;
if (abs(min(dr,t1 - t0)) < VFX_EPSILON * 10)
break;
else if (dr > 0)
t0 = t;
else
t1 = t;
}
tPos *= (r + VFX_EPSILON * colliderSign) / (t + r);
#endif
}
}
if (hit)
{
hitPos = mul(fieldTransform, float4(tPos, 1.0f)).xyz;
hitNormal = VFXSafeNormalize(mul(float4(tPos * colliderSign, 0.0f), invFieldTransform).xyz);
}
";
return Source;
}
}
}