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); } } } }