using System.Collections;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.Serialization;
namespace Fluxy
{
[AddComponentMenu("Physics/FluXY/Target", 800)]
[ExecutionOrder(9999)]
public class FluxyTarget : MonoBehaviour
{
///
/// Material used to splat this target's velocity and density onto containers.
///
[Tooltip("Material used to splat this target's velocity and density onto containers.")]
public Material splatMaterial;
///
/// Amount of splats per update step.
///
[Tooltip("Amount of splats per update step.")]
[Min(0)]
[FormerlySerializedAs("temporalSamples")]
public int rateOverSteps = 1;
///
/// Amount of splats per second.
///
[Tooltip("Amount of splats per second.")]
[Min(0)]
public float rateOverTime = 0;
///
/// Amount of splats per distance unit.
///
[Tooltip("Amount of splats per distance unit.")]
[Min(0)]
public float rateOverDistance = 0;
///
/// Manually override target splat position.
///
[Tooltip("Manually override target splat position.")]
public bool overridePosition = false;
///
/// Coordinate to splat this target at when overriding splat position.
///
[Tooltip("Coordinate to splat this target at when overriding splat position.")]
public Vector2 position = Vector2.zero;
///
/// Randomization applied to the splat position.
///
[Tooltip("Randomization applied to the splat position.")]
[Range(0, 1)]
public float positionRandomness = 0;
///
/// Manually override target splat position.
///
[Tooltip("Manually override target splat rotation.")]
public bool overrideRotation = true;
///
/// Rotation of the target's shape when splatted.
///
[Tooltip("Rotation of the target's shape when splatted.")]
public float rotation = 0;
///
/// Randomization applied to the splat rotation.
///
[Tooltip("Randomization applied to the splat rotation.")]
[Range(0, 1)]
public float rotationRandomness = 0;
///
/// Scales splat size based on distance from the target to the container's surface.
///
[Tooltip("Scales splat size based on distance from the target to the container's surface.")]
public bool scaleWithDistance = true;
///
/// Scales splat based on maximum transform scale value.
///
[Tooltip("Scales splat based on maximum transform scale value.")]
public bool scaleWithTransform = false;
///
/// Scale of the target's shape when splatted.
///
[Tooltip("Scale of the target's shape when splatted.")]
public Vector2 scale = new Vector2(0.1f,0.1f);
///
/// Randomization applied to the splat size.
///
[Tooltip("Randomization applied to the splat size.")]
[Range(0, 1)]
[FormerlySerializedAs("sizeRandomness")]
public float scaleRandomness = 0;
///
/// Amount of velocity splatted.
///
[Range(0,1)]
public float velocityWeight = 1;
///
/// Texture defining the target's splat shape.
///
[Tooltip("Texture defining the target's splat shape.")]
public Texture velocityTexture;
///
/// Maximum relative velocity between a container and this target.
///
[Min(0)]
[Tooltip("Maximum relative velocity between a container and this target.")]
public float maxRelativeVelocity = 8;
///
/// Local-space scale applied to this target's velocity vector.
///
[Tooltip("Local-space scale applied to this target's velocity vector.")]
public Vector3 velocityScale = Vector3.one;
///
/// Maximum relative angular velocity between a container and this target.
///
[Min(0)]
[Tooltip("Maximum relative angular velocity between a container and this target.")]
public float maxRelativeAngularVelocity = 12;
///
/// Scale applied to this target's angular velocity.
///
[Tooltip("Scale applied to this target's angular velocity.")]
public float angularVelocityScale = 1;
///
/// Local-space force applied by this target, regardless of its velocity
///
[Tooltip("Local-space force applied by this target, regardless of its velocity")]
public Vector3 force = Vector3.zero;
///
/// Local-space torque applied by this target, regardless of its angular velocity
///
[Tooltip("Local-space torque applied by this target, regardless of its angular velocity")]
public float torque = 0;
///
/// Amount of density splatted.
///
[Range(0, 1)]
public float densityWeight = 1;
///
/// Texture defining the target's splat shape.
///
[Tooltip("Texture defining the target's splat shape.")]
public Texture densityTexture;
///
/// Blend mode used for source fragments.
///
[Tooltip("Blend mode used for source fragments.")]
public BlendMode srcBlend = BlendMode.SrcAlpha;
///
/// Blend mode used for destination fragments.
///
[Tooltip("Blend mode used for destination fragments.")]
public BlendMode dstBlend = BlendMode.OneMinusSrcAlpha;
///
/// Blend operation used when splatting density.
///
[Tooltip("Blend operation used when splatting density.")]
public BlendOp blendOp = BlendOp.Add;
///
/// Color splatted by this target onto the container's density buffer.
///
[Tooltip("Color splatted by this target onto the container's density buffer.")]
public Color color = Color.white;
///
/// Texture used to generate density and velocity noise.
///
[Tooltip("Texture used to generate density and velocity noise.")]
public Texture noiseTexture;
///
/// Amount of scalar noise modulating density.
///
[Min(0)]
[Tooltip("Amount of scalar noise modulating density.")]
public float densityNoise = 0;
///
/// Non-zero values animate noise by offsetting it.
///
[Min(0)]
[Tooltip("Non-zero values animate noise by offsetting it.")]
public float densityNoiseOffset = 0;
///
/// Tiling scale of density noise.
///
[Min(0)]
[Tooltip("Tiling scale of density noise.")]
public float densityNoiseTiling= 1;
///
/// Amount of curl noise added to velocity.
///
[Min(0)]
[Tooltip("Amount of curl noise added to velocity.")]
public float velocityNoise = 0;
///
/// Non-zero values animate noise by offsetting it.
///
[Min(0)]
[Tooltip("Non-zero values animate noise by offsetting it.")]
public float velocityNoiseOffset = 0;
///
/// Tiling scale of velocity noise.
///
[Min(0)]
[Tooltip("Tiling scale of velocity noise.")]
public float velocityNoiseTiling = 1;
private Vector3 oldPosition;
private Quaternion oldRotation;
private float timeAccumulator = 0;
private float distanceAccumulator = 0;
private int timeSplats = 0;
public delegate void SplatCallback(FluxyTarget target, FluxyContainer container, FluxyStorage.Framebuffer fb, in Vector4 rect);
public event SplatCallback OnSplat;
public Vector3 velocity
{
get { return (transform.position - oldPosition) / Time.deltaTime; }
}
public Vector3 angularVelocity
{
get
{
Quaternion rotationDelta = transform.rotation * Quaternion.Inverse(oldRotation);
return new Vector3(rotationDelta.x, rotationDelta.y, rotationDelta.z) * 2.0f / Time.deltaTime;
}
}
public void OnEnable()
{
SetOldState();
}
private void Update()
{
// update rate over time:
if (rateOverTime > 0)
{
timeAccumulator += Time.deltaTime * rateOverTime;
timeSplats = Mathf.FloorToInt(timeAccumulator);
timeAccumulator -= timeSplats;
}
else
{
timeAccumulator = 0;
timeSplats = 0;
}
}
private void LateUpdate()
{
SetOldState();
}
private void SetOldState()
{
oldPosition = transform.position;
oldRotation = transform.rotation;
}
private float GetAspectRatio()
{
if (densityTexture != null)
return densityTexture.width / (float)densityTexture.height;
else if (velocityTexture != null)
return velocityTexture.width / (float)velocityTexture.height;
else return 1;
}
public virtual void Splat(FluxyContainer container, FluxyStorage.Framebuffer fb, in int tileIndex, in Vector4 rect)
{
if (splatMaterial != null && isActiveAndEnabled)
{
Quaternion worldToContainerR = Quaternion.Inverse(container.transform.rotation);
var lossyScale = transform.lossyScale;
float maxScale = Mathf.Max(Mathf.Max(lossyScale.x, lossyScale.y), lossyScale.z);
// calculate relative velocities in UV space:
Vector3 relativeVelocity = container.TransformWorldVectorToUVSpace(velocity - container.velocity * container.velocityScale, rect);
float relativeAngularVel = container.TransformWorldVectorToUVSpace(angularVelocity, rect).z -
container.TransformWorldVectorToUVSpace(container.angularVelocity, rect).z * container.velocityScale;
// clamp and scale linear vel:
float speed = relativeVelocity.magnitude;
if (speed > FluxyUtils.epsilon)
{
relativeVelocity /= speed;
relativeVelocity *= Mathf.Min(speed, maxRelativeVelocity);
}
Vector4 vel = Vector3.Scale(relativeVelocity, velocityScale) + force;
vel.w = velocityWeight;
// clamp and scale angular vel:
relativeAngularVel = Mathf.Clamp(relativeAngularVel, -maxRelativeAngularVelocity, maxRelativeAngularVelocity) * angularVelocityScale;
relativeAngularVel += torque;
// pass tile index and blend mode to shader.
splatMaterial.SetInt("_TileIndex", tileIndex);
splatMaterial.SetInt("_SrcBlend", (int)srcBlend);
splatMaterial.SetInt("_DstBlend", (int)dstBlend);
splatMaterial.SetInt("_BlendOp", (int)blendOp);
splatMaterial.SetTexture("_Noise", noiseTexture);
var velocityNoiseParams = new Vector3(velocityNoise, velocityNoiseOffset, velocityNoiseTiling);
var densityNoiseParams = new Vector3(densityNoise, densityNoiseOffset, densityNoiseTiling);
// calculate texture aspect ratio:
float aspectRatio = GetAspectRatio();
// update rate over distance:
int distanceSplats;
if (rateOverDistance > 0)
{
distanceAccumulator += Vector3.Distance(transform.position, oldPosition) * rateOverDistance;
distanceSplats = Mathf.FloorToInt(distanceAccumulator);
distanceAccumulator -= distanceSplats;
}
else
{
distanceAccumulator = 0;
distanceSplats = 0;
}
// calculate amount of splats:
int totalSplats = rateOverSteps + timeSplats + distanceSplats;
// calculate splat color:
var splatColor = new Color(color.r, color.g, color.b, color.a * densityWeight);
if (QualitySettings.activeColorSpace == ColorSpace.Linear)
splatColor = splatColor.linear;
// splat multiple times, interpolating between last position and current position:
for (int i = 1; i <= totalSplats; ++i)
{
float interpolationFactor = i / (float)totalSplats;
// randomize position/scale/orientation:
Vector4 randomOffset = (Vector4)Random.insideUnitCircle * positionRandomness;
float randomScale = Random.Range(-scaleRandomness, scaleRandomness) * 0.5f;
float randomRotation = Random.Range(-rotationRandomness, rotationRandomness) * Mathf.PI;
// pass splat rotation to shader (using lerp instead of slerp as rotation angle will be small).
float orientation = rotation;
if (!overrideRotation)
orientation = -(worldToContainerR * Quaternion.Lerp(oldRotation, transform.rotation, interpolationFactor)).eulerAngles.z;
splatMaterial.SetFloat("_SplatRotation", orientation * Mathf.Deg2Rad + randomRotation);
// pass splat scale to shader:
Vector2 projectionSize = scale + new Vector2(randomScale, randomScale);
if (scaleWithTransform)
projectionSize *= maxScale;
// pass splat position to shader:
Vector4 projection;
if (overridePosition)
projection = new Vector4(position.x, position.y, projectionSize.x * aspectRatio, projectionSize.y);
else
{
Vector3 targetPos = Vector3.Lerp(oldPosition, transform.position, interpolationFactor);
projection = container.ProjectTarget(targetPos, projectionSize, aspectRatio, scaleWithDistance);
}
splatMaterial.SetVector("_SplatTransform", projection + randomOffset);
// splat state:
splatMaterial.SetVector("_DensityNoiseParams", densityNoiseParams);
splatMaterial.SetVector("_SplatWeights", splatColor);
Graphics.Blit(densityTexture, fb.stateA, splatMaterial, 0);
// splat velocity
splatMaterial.SetVector("_VelocityNoiseParams", velocityNoiseParams);
splatMaterial.SetFloat("_AngularVelocity", relativeAngularVel);
splatMaterial.SetVector("_SplatWeights", vel);
splatMaterial.SetTexture("_Velocity", velocityTexture);
Graphics.Blit(densityTexture, fb.velocityA, splatMaterial, 1);
}
OnSplat?.Invoke(this, container, fb, rect);
}
}
}
}