/**************************************/
/* LIQUID VOLUME PRO 2 */
/* Created by Kronnect */
/**************************************/
// Enable floating point render textures
#define LIQUID_VOLUME_FP_RENDER_TEXTURES
// Displays sliced liquid volume
//#define DEBUG_SLICE
using UnityEngine;
using UnityEngine.Rendering;
using System;
using System.Collections.Generic;
using LiquidVolumeFX.MIConvexHull;
using System.Linq;
using Random = UnityEngine.Random;
using UnityEngine.XR;
#if UNITY_EDITOR
using UnityEditor;
#endif
namespace LiquidVolumeFX {
public enum TOPOLOGY {
Sphere = 0,
Cylinder = 1,
Cube = 2,
Irregular = 10
}
public enum DETAIL {
Simple = 0,
SimpleNoFlask = 1,
Default = 10,
DefaultNoFlask = 11,
Multiple = 50,
MultipleNoFlask = 51
}
public enum LEVEL_COMPENSATION {
None = 0,
Fast = 10,
Accurate = 20
}
public enum BuoyancyEffect {
Simple = 0,
PositionOnly = 1,
PositionAndRotation = 2
}
public static class DetailExtensions {
public static bool isMultiple(this DETAIL detail) {
return detail == DETAIL.Multiple || detail == DETAIL.MultipleNoFlask;
}
public static bool allowsRefraction(this DETAIL detail) {
return detail != DETAIL.DefaultNoFlask && detail != DETAIL.MultipleNoFlask;
}
public static bool usesFlask(this DETAIL detail) {
return detail == DETAIL.Simple || detail == DETAIL.Default || detail == DETAIL.Multiple;
}
}
[ExecuteInEditMode]
[HelpURL("https://kronnect.com/support")]
[AddComponentMenu("Effects/Liquid Volume")]
[DisallowMultipleComponent]
public partial class LiquidVolume : MonoBehaviour {
public static bool FORCE_GLES_COMPATIBILITY = false;
///
/// Max number of layers for multiple style
///
public const int MAX_LAYERS = 16;
[Serializable]
public struct LiquidLayer {
[HideInInspector] public string layerName;
public float amount;
[Tooltip("Density defines the order in which layers stack up. Two or more liquids with same density and miscible set to true will mix.")]
public float density;
public Color color;
[Tooltip("If miscible is set to true, this liquid will mix with any other with same density.")]
public bool miscible;
[Range(0, 1)]
public float murkiness;
public Color murkColor;
[Range(0.001f, 0.48f)]
public float scale;
[Range(0, 1), Tooltip("The viscosity determines the influence of global turbulence to this liquid. A value of 0 means sticky while a value of 1 means a very fluid substance.")]
public float viscosity;
[Range(0.001f, 10)]
public float adjustmentSpeed;
[Range(0, 1), Tooltip("Bubbles opacity.")]
public float bubblesOpacity;
[NonSerialized]
public float mixedAmount, mixedScale, mixedMurkiness;
[NonSerialized]
public Color mixedColor;
[NonSerialized]
public Color mixedColor2;
[NonSerialized]
public float mixedBubblesOpacity;
[HideInInspector]
public float baseLevel;
[HideInInspector]
public float currentBaseLevel;
[HideInInspector]
public float currentAmount;
[HideInInspector]
public Color currentColor;
[HideInInspector]
public float currentMurkiness;
[HideInInspector]
public Color currentColor2;
[HideInInspector]
public float currentBubblesOpacity;
public void SetDefaults(int layerIndex) {
amount = 0.1f;
density = 1f;
color = new Color(Random.value, Random.value, Random.value, 0.5f);
murkColor = new Color(0, 0, 0, 1f);
murkiness = 0.5f;
scale = 0.3f;
adjustmentSpeed = 1f;
viscosity = 1f;
bubblesOpacity = 1f;
layerName = "Layer " + layerIndex;
}
public void ResetCurrentStates() {
currentColor = color;
currentColor2 = murkColor;
currentMurkiness = murkiness;
currentAmount = amount;
currentBaseLevel = baseLevel;
currentBubblesOpacity = bubblesOpacity;
}
}
[SerializeField]
TOPOLOGY _topology = TOPOLOGY.Sphere;
public TOPOLOGY topology {
get { return _topology; }
set {
if (_topology != value) {
_topology = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
DETAIL _detail = DETAIL.Default;
public DETAIL detail {
get { return _detail; }
set {
if (_detail != value) {
_detail = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _level = 0.5f;
public float level {
get { return _level; }
set {
if (_level != value) {
if (_detail.isMultiple()) {
Debug.LogWarning("In multiple detail level, fill level is automatically determined by the amount/density values in each layer.");
return;
}
_level = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _levelMultiplier = 1f;
public float levelMultiplier {
get { return _levelMultiplier; }
set {
if (_levelMultiplier != value) {
_levelMultiplier = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
LiquidLayer[] _liquidLayers;
public LiquidLayer[] liquidLayers {
get { return _liquidLayers; }
set {
if (_liquidLayers != value) {
_liquidLayers = value;
lastLayerCount = 0;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 16)]
int _smoothContactSurface = 3;
public int smoothContactSurface {
get { return _smoothContactSurface; }
set {
if (_smoothContactSurface != value) {
_smoothContactSurface = value;
requireLayersUpdate = true;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _ditherStrength = 0.5f;
public float ditherStrength {
get { return _ditherStrength; }
set {
if (_ditherStrength != value) {
_ditherStrength = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.001f, 10)]
float _layersAdjustmentSpeed = 1f;
public float layersAdjustmentSpeed {
get { return _layersAdjustmentSpeed; }
set {
if (_layersAdjustmentSpeed != value) {
_layersAdjustmentSpeed = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _layersAdjustmentCompact = true;
public bool layersAdjustmentCompact {
get { return _layersAdjustmentCompact; }
set {
if (_layersAdjustmentCompact != value) {
_layersAdjustmentCompact = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Tooltip("Uses directional light color")]
bool _useLightColor;
public bool useLightColor {
get { return _useLightColor; }
set {
if (_useLightColor != value) {
_useLightColor = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Light _directionalLight;
public Light directionalLight {
get { return _directionalLight; }
set {
if (_directionalLight != value) {
_directionalLight = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[ColorUsage(true)]
Color _liquidColor1 = new Color(0, 1, 0, 0.1f);
public Color liquidColor1 {
get { return _liquidColor1; }
set {
if (_liquidColor1 != value) {
_liquidColor1 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.1f, 4.85f)]
float _liquidScale1 = 1f;
public float liquidScale1 {
get { return _liquidScale1; }
set {
if (_liquidScale1 != value) {
_liquidScale1 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[ColorUsage(true)]
Color _liquidColor2 = new Color(1, 0, 0, 0.3f);
public Color liquidColor2 {
get { return _liquidColor2; }
set {
if (_liquidColor2 != value) {
_liquidColor2 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(2f, 4.85f)]
float _liquidScale2 = 5f;
public float liquidScale2 {
get { return _liquidScale2; }
set {
if (_liquidScale2 != value) {
_liquidScale2 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _alpha = 1f;
public float alpha {
get { return _alpha; }
set {
if (_alpha != Mathf.Clamp01(value)) {
_alpha = Mathf.Clamp01(value);
UpdateMaterialProperties();
}
}
}
[SerializeField]
[ColorUsage(true)]
Color _emissionColor = new Color(0, 0, 0);
public Color emissionColor {
get { return _emissionColor; }
set {
if (_emissionColor != value) {
_emissionColor = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _ditherShadows = true;
public bool ditherShadows {
get { return _ditherShadows; }
set {
if (_ditherShadows != value) {
_ditherShadows = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _murkiness = 1.0f;
public float murkiness {
get { return _murkiness; }
set {
if (_murkiness != value) {
_murkiness = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1f)]
float _turbulence1 = 0.5f;
public float turbulence1 {
get { return _turbulence1; }
set {
if (_turbulence1 != value) {
_turbulence1 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1f)]
float _turbulence2 = 0.2f;
public float turbulence2 {
get { return _turbulence2; }
set {
if (_turbulence2 != value) {
_turbulence2 = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _frecuency = 1f;
public float frecuency {
get { return _frecuency; }
set {
if (_frecuency != value) {
_frecuency = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0f, 2f)]
float _speed = 1f;
public float speed {
get { return _speed; }
set {
if (_speed != value) {
_speed = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 5f)]
float _sparklingIntensity = 0.1f;
public float sparklingIntensity {
get { return _sparklingIntensity; }
set {
if (_sparklingIntensity != value) {
_sparklingIntensity = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _sparklingAmount = 0.2f;
public float sparklingAmount {
get { return _sparklingAmount; }
set {
if (_sparklingAmount != value) {
_sparklingAmount = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10)]
float _deepObscurance = 2.0f;
public float deepObscurance {
get { return _deepObscurance; }
set {
if (_deepObscurance != value) {
_deepObscurance = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[ColorUsage(true)]
Color _foamColor = new Color(1, 1, 1, 0.65f);
public Color foamColor {
get { return _foamColor; }
set {
if (_foamColor != value) {
_foamColor = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.01f, 1f)]
float _foamScale = 0.2f;
public float foamScale {
get { return _foamScale; }
set {
if (_foamScale != value) {
_foamScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 0.1f)]
float _foamThickness = 0.04f;
public float foamThickness {
get { return _foamThickness; }
set {
if (_foamThickness != value) {
_foamThickness = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(-1, 1)]
float _foamDensity = 0.5f;
public float foamDensity {
get { return _foamDensity; }
set {
if (_foamDensity != value) {
_foamDensity = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(4, 100)]
float _foamWeight = 10f;
public float foamWeight {
get { return _foamWeight; }
set {
if (_foamWeight != value) {
_foamWeight = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _foamTurbulence = 1f;
public float foamTurbulence {
get { return _foamTurbulence; }
set {
if (_foamTurbulence != value) {
_foamTurbulence = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _foamVisibleFromBottom = true;
public bool foamVisibleFromBottom {
get { return _foamVisibleFromBottom; }
set {
if (_foamVisibleFromBottom != value) {
_foamVisibleFromBottom = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _smokeEnabled = true;
public bool smokeEnabled {
get { return _smokeEnabled; }
set {
if (_smokeEnabled != value) {
_smokeEnabled = value;
UpdateMaterialProperties();
}
}
}
[ColorUsage(true)]
[SerializeField]
Color _smokeColor = new Color(0.7f, 0.7f, 0.7f, 0.25f);
public Color smokeColor {
get { return _smokeColor; }
set {
if (_smokeColor != value) {
_smokeColor = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.01f, 1f)]
float _smokeScale = 0.25f;
public float smokeScale {
get { return _smokeScale; }
set {
if (_smokeScale != value) {
_smokeScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10f)]
float _smokeBaseObscurance = 2.0f;
public float smokeBaseObscurance {
get { return _smokeBaseObscurance; }
set {
if (_smokeBaseObscurance != value) {
_smokeBaseObscurance = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10f)]
float _smokeHeightAtten;
public float smokeHeightAtten {
get { return _smokeHeightAtten; }
set {
if (_smokeHeightAtten != value) {
_smokeHeightAtten = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 20f)]
float _smokeSpeed = 5.0f;
public float smokeSpeed {
get { return _smokeSpeed; }
set {
if (_smokeSpeed != value) {
_smokeSpeed = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _fixMesh;
public bool fixMesh {
get { return _fixMesh; }
set {
if (_fixMesh != value) {
_fixMesh = value;
UpdateMaterialProperties();
}
}
}
public Mesh originalMesh;
public Vector3 originalPivotOffset;
[SerializeField]
Vector3 _pivotOffset;
public Vector3 pivotOffset {
get { return _pivotOffset; }
set {
if (_pivotOffset != value) {
_pivotOffset = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _autoCloseMesh;
public bool autoCloseMesh {
get { return _autoCloseMesh; }
set {
if (_autoCloseMesh != value) {
_autoCloseMesh = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1.5f)]
float _upperLimit = 1.5f;
public float upperLimit {
get { return _upperLimit; }
set {
if (_upperLimit != value) {
_upperLimit = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(-1.5f, 1.5f)]
float _lowerLimit = -1.5f;
public float lowerLimit {
get { return _lowerLimit; }
set {
if (_lowerLimit != value) {
_lowerLimit = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
int _subMeshIndex = -1;
public int subMeshIndex {
get { return _subMeshIndex; }
set {
if (_subMeshIndex != value) {
_subMeshIndex = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Material _flaskMaterial;
public Material flaskMaterial {
get { return _flaskMaterial; }
set {
if (_flaskMaterial != value) {
_flaskMaterial = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _flaskThickness = 0.03f;
public float flaskThickness {
get { return _flaskThickness; }
set {
if (_flaskThickness != value) {
_flaskThickness = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _glossinessInternal = 0.3f;
public float glossinessInternal {
get { return _glossinessInternal; }
set {
if (_glossinessInternal != value) {
_glossinessInternal = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _scatteringEnabled = false;
public bool scatteringEnabled {
get { return _scatteringEnabled; }
set {
if (_scatteringEnabled != value) {
_scatteringEnabled = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(1, 16)]
int _scatteringPower = 5;
public int scatteringPower {
get { return _scatteringPower; }
set {
if (_scatteringPower != value) {
_scatteringPower = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10)]
float _scatteringAmount = 0.3f;
public float scatteringAmount {
get { return _scatteringAmount; }
set {
if (_scatteringAmount != value) {
_scatteringAmount = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _refractionBlur = true;
public bool refractionBlur {
get { return _refractionBlur; }
set {
if (_refractionBlur != value) {
_refractionBlur = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1)]
float _blurIntensity = 0.75f;
public float blurIntensity {
get { return _blurIntensity; }
set {
if (_blurIntensity != Mathf.Clamp01(value)) {
_blurIntensity = Mathf.Clamp01(value);
UpdateMaterialProperties();
}
}
}
[SerializeField]
int _liquidRaySteps = 10;
public int liquidRaySteps {
get { return _liquidRaySteps; }
set {
if (_liquidRaySteps != value) {
_liquidRaySteps = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
int _foamRaySteps = 7;
public int foamRaySteps {
get { return _foamRaySteps; }
set {
if (_foamRaySteps != value) {
_foamRaySteps = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
int _smokeRaySteps = 5;
public int smokeRaySteps {
get { return _smokeRaySteps; }
set {
if (_smokeRaySteps != value) {
_smokeRaySteps = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Texture2D _bumpMap;
public Texture2D bumpMap {
get { return _bumpMap; }
set {
if (_bumpMap != value) {
_bumpMap = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 1f)]
float _bumpStrength = 1f;
public float bumpStrength {
get { return _bumpStrength; }
set {
if (_bumpStrength != value) {
_bumpStrength = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10f)]
float _bumpDistortionScale = 1f;
public float bumpDistortionScale {
get { return _bumpDistortionScale; }
set {
if (_bumpDistortionScale != value) {
_bumpDistortionScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Vector2 _bumpDistortionOffset;
public Vector2 bumpDistortionOffset {
get { return _bumpDistortionOffset; }
set {
if (_bumpDistortionOffset != value) {
_bumpDistortionOffset = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Texture2D _distortionMap;
public Texture2D distortionMap {
get { return _distortionMap; }
set {
if (_distortionMap != value) {
_distortionMap = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Texture2D _texture;
public Texture2D texture {
get { return _texture; }
set {
if (_texture != value) {
_texture = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Vector2
_textureScale = Vector2.one;
public Vector2 textureScale {
get { return _textureScale; }
set {
if (_textureScale != value) {
_textureScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Vector2
_textureOffset;
public Vector2 textureOffset {
get { return _textureOffset; }
set {
if (_textureOffset != value) {
_textureOffset = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 10f)]
float
_distortionAmount = 1f;
public float distortionAmount {
get { return _distortionAmount; }
set {
if (_distortionAmount != value) {
_distortionAmount = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _depthAware;
public bool depthAware {
get { return _depthAware; }
set {
if (_depthAware != value) {
_depthAware = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _depthAwareOffset;
public float depthAwareOffset {
get { return _depthAwareOffset; }
set {
if (_depthAwareOffset != value) {
_depthAwareOffset = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _irregularDepthDebug = false;
public bool irregularDepthDebug {
get { return _irregularDepthDebug; }
set {
if (_irregularDepthDebug != value) {
_irregularDepthDebug = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _depthAwareCustomPass;
public bool depthAwareCustomPass {
get { return _depthAwareCustomPass; }
set {
if (_depthAwareCustomPass != value) {
_depthAwareCustomPass = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _depthAwareCustomPassDebug = false;
public bool depthAwareCustomPassDebug {
get { return _depthAwareCustomPassDebug; }
set {
if (_depthAwareCustomPassDebug != value) {
_depthAwareCustomPassDebug = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0, 5f)]
float _doubleSidedBias = 0.001f;
public float doubleSidedBias {
get { return _doubleSidedBias; }
set {
if (_doubleSidedBias != value) {
_doubleSidedBias = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _backDepthBias;
public float backDepthBias {
get { return _backDepthBias; }
set {
if (value < 0) value = 0;
if (_backDepthBias != value) {
_backDepthBias = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
LEVEL_COMPENSATION _rotationLevelCompensation = LEVEL_COMPENSATION.None;
public LEVEL_COMPENSATION rotationLevelCompensation {
get { return _rotationLevelCompensation; }
set {
if (_rotationLevelCompensation != value) {
_rotationLevelCompensation = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _ignoreGravity;
public bool ignoreGravity {
get { return _ignoreGravity; }
set {
if (_ignoreGravity != value) {
_ignoreGravity = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _reactToForces;
public bool reactToForces {
get { return _reactToForces; }
set {
if (_reactToForces != value) {
_reactToForces = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Vector3 _extentsScale = Vector3.one;
public Vector3 extentsScale {
get { return _extentsScale; }
set {
if (_extentsScale != value) {
_extentsScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(1, 3)]
int _noiseVariation = 1;
public int noiseVariation {
get { return _noiseVariation; }
set {
if (_noiseVariation != value) {
_noiseVariation = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
bool _allowViewFromInside = false;
public bool allowViewFromInside {
get { return _allowViewFromInside; }
set {
if (_allowViewFromInside != value) {
_allowViewFromInside = value;
lastDistanceToCam = -1;
CheckInsideOut();
}
}
}
[SerializeField]
bool
_debugSpillPoint = false;
public bool debugSpillPoint {
get { return _debugSpillPoint; }
set {
if (_debugSpillPoint != value) {
_debugSpillPoint = value;
}
}
}
[SerializeField]
int
_renderQueue = 3001;
public int renderQueue {
get { return _renderQueue; }
set {
if (_renderQueue != value) {
_renderQueue = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
Cubemap _reflectionTexture;
public Cubemap reflectionTexture {
get { return _reflectionTexture; }
set {
if (_reflectionTexture != value) {
_reflectionTexture = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.1f, 5f)]
float _physicsMass = 1f;
public float physicsMass {
get { return _physicsMass; }
set {
if (_physicsMass != value) {
_physicsMass = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
[Range(0.0f, 0.2f)]
float _physicsAngularDamp = 0.02f;
public float physicsAngularDamp {
get { return _physicsAngularDamp; }
set {
if (_physicsAngularDamp != value) {
_physicsAngularDamp = value;
UpdateMaterialProperties();
}
}
}
#region Bubbles properties
[SerializeField]
int _bubblesAmount = 32;
public int bubblesAmount {
get { return _bubblesAmount; }
set {
if (_bubblesAmount != value) {
_bubblesAmount = value;
requireBubblesUpdate = true;
UpdateMaterialProperties();
}
}
}
[SerializeField, Range(1, 16)]
int _bubblesSizeMin = 2;
public int bubblesSizeMin {
get { return _bubblesSizeMin; }
set {
if (_bubblesSizeMin != value) {
_bubblesSizeMin = value;
requireBubblesUpdate = true;
UpdateMaterialProperties();
}
}
}
[SerializeField, Range(1, 16)]
int _bubblesSizeMax = 4;
public int bubblesSizeMax {
get { return _bubblesSizeMax; }
set {
if (_bubblesSizeMax != value) {
_bubblesSizeMax = value;
requireBubblesUpdate = true;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _bubblesScale = 3f;
public float bubblesScale {
get { return _bubblesScale; }
set {
if (_bubblesScale != value) {
_bubblesScale = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _bubblesBrightness = 1;
public float bubblesBrightness {
get { return _bubblesBrightness; }
set {
if (_bubblesBrightness != value) {
_bubblesBrightness = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
float _bubblesVerticalSpeed = 1;
public float bubblesVerticalSpeed {
get { return _bubblesVerticalSpeed; }
set {
if (_bubblesVerticalSpeed != value) {
_bubblesVerticalSpeed = value;
UpdateMaterialProperties();
}
}
}
[SerializeField]
int _bubblesSeed = 1;
public int bubblesSeed {
get { return _bubblesSeed; }
set {
if (_bubblesSeed != value) {
_bubblesSeed = value;
requireBubblesUpdate = true;
UpdateMaterialProperties();
}
}
}
#endregion
#region Point Light support
[Serializable]
public struct PointLightParams {
[HideInInspector]
public string name;
[NonSerialized]
public Vector3 localPosition;
[NonSerialized]
public Vector3 accel;
[NonSerialized]
public float nextTargetTime;
public Vector3 targetPos;
public bool randomTarget;
public Vector3 randomTargetCenter;
public float randomTargetRadius;
public float nextTargetInterval;
public float moveSpeed;
public float lightRange;
public float intensityMin, intensityMax;
public float intensitySpeed;
public Color color1, color2;
public float colorSpeed;
}
[SerializeField]
bool _pointLightsEnabled;
public bool pointLightsEnabled {
get { return _pointLightsEnabled; }
set {
if (_pointLightsEnabled != value) {
_pointLightsEnabled = value;
UpdateMaterialProperties();
}
}
}
public PointLightParams[] pointLightParams;
public float pointLightsScatteringAmount = 0.8f;
public float pointLightsIntensity = 1f;
public float pointLightInsideAtten;
#endregion
#if LIQUID_VOLUME_FP_RENDER_TEXTURES
public static bool useFPRenderTextures => true;
#else
public static bool useFPRenderTextures => false;
#endif
// ---- INTERNAL CODE ----
const int SHADER_KEYWORD_DEPTH_AWARE_INDEX = 0;
const int SHADER_KEYWORD_DEPTH_AWARE_CUSTOM_PASS_INDEX = 1;
const int SHADER_KEYWORD_IGNORE_GRAVITY_INDEX = 2;
const int SHADER_KEYWORD_NON_AABB_INDEX = 3;
const int SHADER_KEYWORD_TOPOLOGY_INDEX = 4;
const int SHADER_KEYWORD_REFRACTION_INDEX = 5;
const string SHADER_KEYWORD_DEPTH_AWARE = "LIQUID_VOLUME_DEPTH_AWARE";
const string SHADER_KEYWORD_DEPTH_AWARE_CUSTOM_PASS = "LIQUID_VOLUME_DEPTH_AWARE_PASS";
const string SHADER_KEYWORD_NON_AABB = "LIQUID_VOLUME_NON_AABB";
const string SHADER_KEYWORD_IGNORE_GRAVITY = "LIQUID_VOLUME_IGNORE_GRAVITY";
const string SHADER_KEYWORD_SPHERE = "LIQUID_VOLUME_SPHERE";
const string SHADER_KEYWORD_CUBE = "LIQUID_VOLUME_CUBE";
const string SHADER_KEYWORD_CYLINDER = "LIQUID_VOLUME_CYLINDER";
const string SHADER_KEYWORD_IRREGULAR = "LIQUID_VOLUME_IRREGULAR";
const string SHADER_KEYWORD_FP_RENDER_TEXTURE = "LIQUID_VOLUME_FP_RENDER_TEXTURES";
const string SHADER_KEYWORD_USE_REFRACTION = "LIQUID_VOLUME_USE_REFRACTION";
const string SPILL_POINT_GIZMO = "SpillPointGizmo";
[NonSerialized]
public Material liqMat;
Material liqMatSimple, liqMatDefaultNoFlask, liqMatMultipleNoFlask;
Mesh mesh;
[NonSerialized]
public Renderer mr;
readonly static List mrSharedMaterials = new List();
Vector3 lastPosition, lastScale;
Quaternion lastRotation;
string[] shaderKeywords;
bool camInside;
float lastDistanceToCam;
DETAIL currentDetail;
Vector4 turb, shaderTurb;
float turbulenceSpeed, murkinessSpeed, bubblesVerticalSpeedAcum;
float liquidLevelPos;
bool shouldUpdateMaterialProperties;
int currentNoiseVariation;
float levelMultipled;
// Multiple layers
Color[] layersTextureColors, layersTextureColors2, layersTextureColorsTemp;
Vector4[] layersProperties;
Texture2D layersPropertiesTex, layersColorsTex, layersColors2Tex;
Texture2D noise3DUnwrapped;
Texture3D[] noise3DTex;
Color[][] colors3D, colors3DWithBubbles;
Color[] layersColorsTexContents, layersColors2TexContents;
Color[] layersPropertiesTexContents;
float[] layersSum;
[NonSerialized]
public bool requireLayersUpdate;
[NonSerialized]
public bool requireBubblesUpdate;
int[] sortedLayers;
int sortedLayersCount;
int lastLayerCount;
bool immediate;
// Mesh info
Vector3[] verticesUnsorted, verticesSorted;
static Vector3[] rotatedVertices;
int[] verticesIndices;
float volumeRef, lastLevelVolumeRef;
// Physics
Vector3 prevVelocity, prev2Velocity, inertia, lastAvgVelocity;
float angularVelocity, angularInertia;
float turbulenceDueForces;
Quaternion liquidRot;
float prevThickness;
// Spill point debug
GameObject spillPointGizmo;
static string[] defaultContainerNames = {
"GLASS",
"CONTAINER",
"BOTTLE",
"POTION",
"FLASK",
"LIQUID"
};
// Point light effects
Color[] pointLightColorBuffer;
Vector4[] pointLightPositionBuffer;
int lastPointLightCount;
#region Gameloop events
void OnEnable() {
if (!gameObject.activeInHierarchy)
return;
levelMultipled = _level * _levelMultiplier;
turb.z = 1f;
turbulenceDueForces = 0f;
turbulenceSpeed = 1f;
liquidRot = transform.rotation;
currentDetail = _detail;
currentNoiseVariation = -1;
lastPosition = transform.position;
lastRotation = transform.rotation;
lastScale = transform.localScale;
prevThickness = _flaskThickness;
if (_depthAwareCustomPass && transform.parent == null) {
_depthAwareCustomPass = false;
}
InitLayers();
UpdateMaterialPropertiesNow();
PointLightsInitialize();
if (!Application.isPlaying) {
if (_detail.isMultiple()) {
requireLayersUpdate = true;
}
shouldUpdateMaterialProperties = true;
}
}
void Reset() {
// Try to assign propert topology based on mesh
if (mesh == null)
return;
if (mesh.vertexCount == 24) {
topology = TOPOLOGY.Cube;
} else {
Renderer renderer = GetComponent();
if (renderer == null) {
if (mesh.bounds.extents.y > mesh.bounds.extents.x) {
topology = TOPOLOGY.Cylinder;
}
} else if (renderer.bounds.extents.y > renderer.bounds.extents.x) {
topology = TOPOLOGY.Cylinder;
if (!Application.isPlaying) {
if (transform.rotation.eulerAngles != Vector3.zero && (mesh.bounds.extents.y <= mesh.bounds.extents.x || mesh.bounds.extents.y <= mesh.bounds.extents.z)) {
Debug.LogWarning("Intrinsic model rotation detected. Consider using the Bake Transform and/or Center Pivot options in Advanced section.");
}
}
}
}
}
void OnDestroy() {
RestoreOriginalMesh();
liqMat = null;
if (liqMatSimple != null) {
DestroyImmediate(liqMatSimple);
liqMatSimple = null;
}
if (liqMatDefaultNoFlask != null) {
DestroyImmediate(liqMatDefaultNoFlask);
liqMatDefaultNoFlask = null;
}
if (liqMatMultipleNoFlask != null) {
DestroyImmediate(liqMatMultipleNoFlask);
liqMatMultipleNoFlask = null;
}
if (noise3DTex != null) {
for (int k = 0; k < noise3DTex.Length; k++) {
Texture3D tex = noise3DTex[k];
if (tex != null && tex.name.Contains("Clone")) {
DestroyImmediate(tex);
noise3DTex[k] = null;
}
}
}
LiquidVolumeDepthPrePassRenderFeature.RemoveLiquidFromBackRenderers(this);
LiquidVolumeDepthPrePassRenderFeature.RemoveLiquidFromFrontRenderers(this);
}
void RenderObject() {
var act = gameObject.activeInHierarchy && enabled;
if (shouldUpdateMaterialProperties || !Application.isPlaying) {
shouldUpdateMaterialProperties = false;
UpdateMaterialPropertiesNow();
}
if (act && _allowViewFromInside) {
CheckInsideOut();
}
if (_detail.isMultiple()) {
AnimateLayers();
}
UpdateAnimations();
if (_pointLightsEnabled) {
UpdateLightEffects();
}
if (!act || _topology != TOPOLOGY.Irregular) {
LiquidVolumeDepthPrePassRenderFeature.RemoveLiquidFromBackRenderers(this);
} else if (_topology == TOPOLOGY.Irregular) {
LiquidVolumeDepthPrePassRenderFeature.AddLiquidToBackRenderers(this);
}
Transform parent = transform.parent;
if (parent != null) {
Renderer parentRenderer = GetComponentInParent();
if (!act || !_depthAwareCustomPass) {
LiquidVolumeDepthPrePassRenderFeature.RemoveLiquidFromFrontRenderers(this);
} else if (_depthAwareCustomPass) {
LiquidVolumeDepthPrePassRenderFeature.AddLiquidToFrontRenderers(this);
}
}
if (_debugSpillPoint) {
UpdateSpillPointGizmo();
}
#if UNITY_EDITOR
// Since Unity materials do not support color/vector arrays we need to submit them everytime to avoid losing the uniform values inside Unity Editor
if (_detail.isMultiple()) {
UploadMaterialArrays();
}
#endif
}
public void OnWillRenderObject() {
RenderObject();
}
void FixedUpdate() {
turbulenceSpeed += Time.deltaTime * 3f * _speed;
liqMat.SetFloat(ShaderParams.TurbulenceSpeed, turbulenceSpeed * 4f);
murkinessSpeed += Time.deltaTime * 0.05f * (shaderTurb.x + shaderTurb.y);
liqMat.SetFloat(ShaderParams.MurkinessSpeed, murkinessSpeed);
bubblesVerticalSpeedAcum += Time.deltaTime * _bubblesVerticalSpeed;
if (_detail.isMultiple()) {
UpdateBubblesProperties();
}
}
void UpdateBubblesProperties() {
if (_bubblesBrightness < 0)
_bubblesBrightness = 0;
liqMat.SetVector(ShaderParams.BubblesData, new Vector4(100f / (1f + _bubblesScale), bubblesVerticalSpeedAcum, _bubblesBrightness, 0));
}
void OnDidApplyAnimationProperties() { // support for animating property based fields
shouldUpdateMaterialProperties = true;
}
#endregion
#region Internal stuff
struct MeshCache {
public Vector3[] verticesSorted;
public Vector3[] verticesUnsorted;
public int[] indices;
}
static readonly Dictionary meshCache = new Dictionary();
public void ClearMeshCache() {
meshCache.Clear();
}
void ReadVertices() {
if (mesh == null)
return;
if (!meshCache.TryGetValue(mesh, out MeshCache meshData)) {
if (!mesh.isReadable) {
Debug.Log("Mesh " + mesh.name + " is not readable. Please select your mesh and enable the Read/Write Enabled option.");
}
verticesUnsorted = mesh.vertices;
verticesIndices = mesh.triangles;
int vertexCount = verticesUnsorted.Length;
if (verticesSorted == null || verticesSorted.Length != vertexCount) {
verticesSorted = new Vector3[vertexCount];
}
Array.Copy(verticesUnsorted, verticesSorted, vertexCount);
Array.Sort(verticesSorted, vertexComparer);
meshData.verticesUnsorted = verticesUnsorted;
meshData.indices = verticesIndices;
meshData.verticesSorted = verticesSorted;
if (meshCache.Count > 64) {
// Clear cache to avoid memory overrun
ClearMeshCache();
}
meshCache[mesh] = meshData;
} else {
verticesUnsorted = meshData.verticesUnsorted;
verticesIndices = meshData.indices;
verticesSorted = meshData.verticesSorted;
}
}
int vertexComparer(Vector3 v0, Vector3 v1) {
if (v1.y < v0.y) return -1;
if (v1.y > v0.y) return 1;
return 0;
}
void UpdateAnimations() {
// Check proper scale
switch (topology) {
case TOPOLOGY.Sphere:
if (transform.localScale.y != transform.localScale.x || transform.localScale.z != transform.localScale.x)
transform.localScale = new Vector3(transform.localScale.x, transform.localScale.x, transform.localScale.x);
break;
case TOPOLOGY.Cylinder:
if (transform.localScale.z != transform.localScale.x)
transform.localScale = new Vector3(transform.localScale.x, transform.localScale.y, transform.localScale.x);
break;
}
if (liqMat != null) {
Vector3 turbDir = Vector3.right;
Quaternion rot = transform.rotation;
if (_reactToForces) {
Quaternion instantRot = transform.rotation;
float dt = Time.smoothDeltaTime;
if (Application.isPlaying && dt > 0) {
Vector3 instantVelocity = (transform.position - lastPosition) / dt;
Vector3 avgVelocity = (prev2Velocity + prevVelocity + instantVelocity) / 3f;
prev2Velocity = prevVelocity;
prevVelocity = instantVelocity;
Vector3 instantAccel = (avgVelocity - lastAvgVelocity);
lastAvgVelocity = avgVelocity;
inertia += avgVelocity;
float accelMag = instantAccel.magnitude;
float force = Mathf.Max(accelMag / _physicsMass - _physicsAngularDamp, 0f);
angularInertia += force;
angularVelocity += angularInertia;
if (angularVelocity > 0) {
angularInertia -= Mathf.Abs(angularVelocity) * _physicsMass / 100f;
} else if (angularVelocity < 0) {
angularInertia += Mathf.Abs(angularVelocity) * _physicsMass / 100f;
}
float damp = 1f - _physicsAngularDamp;
angularInertia *= damp;
inertia *= damp;
float mag = Mathf.Clamp(angularVelocity, -90f, 90f);
if (inertia.x != 0 || inertia.z != 0) {
turbDir = inertia.normalized;
}
Vector3 axis = Vector3.Cross(turbDir, Vector3.down);
instantRot = Quaternion.AngleAxis(mag, axis);
float cinematic = Mathf.Abs(angularInertia) + Mathf.Abs(angularVelocity);
turbulenceDueForces = Mathf.Min(0.5f / _physicsMass, turbulenceDueForces + cinematic / 1000f);
turbulenceDueForces *= damp;
} else {
turbulenceDueForces = 0;
}
if (_topology == TOPOLOGY.Sphere) {
liquidRot = Quaternion.Lerp(liquidRot, instantRot, 0.1f);
rot = liquidRot;
}
} else if (turbulenceDueForces > 0) {
turbulenceDueForces *= 0.1f;
}
Matrix4x4 m = Matrix4x4.TRS(Vector3.zero, rot, Vector3.one);
liqMat.SetMatrix(ShaderParams.RotationMatrix, m.inverse);
if (_topology != TOPOLOGY.Sphere) {
float tx = turbDir.x;
turbDir.x += (turbDir.z - turbDir.x) * 0.25f;
turbDir.z += (tx - turbDir.z) * 0.25f;
}
turb.z = turbDir.x;
turb.w = turbDir.z;
}
bool hasRotated = transform.rotation != lastRotation;
if (_reactToForces || hasRotated || transform.position != lastPosition || transform.localScale != lastScale) {
UpdateLevels(hasRotated);
}
}
public void PointLightsRandomize() {
PointLightsInitialize(true);
}
void PointLightsInitialize(bool randomize = false) {
if (pointLightParams == null) {
pointLightParams = new PointLightParams[6];
randomize = true;
} else if (pointLightParams.Length > 6) {
Array.Resize(ref pointLightParams, 6);
}
if (!_pointLightsEnabled) {
UpdateLightEffects();
return;
}
if (randomize) {
for (int k = 0; k < pointLightParams.Length; k++) {
pointLightParams[k].localPosition = Vector3.zero;
pointLightParams[k].randomTargetCenter = new Vector3(0, -0.5f + _level * 0.5f, 0);
pointLightParams[k].color1 = new Color(Random.value, Random.value, Random.value);
pointLightParams[k].color2 = new Color(Random.value, Random.value, Random.value);
pointLightParams[k].colorSpeed = Random.Range(0.5f, 2f);
pointLightParams[k].intensityMin = Random.Range(0.5f, 1.5f);
pointLightParams[k].intensityMax = pointLightParams[k].intensityMin + Random.value * 1.5f;
pointLightParams[k].intensitySpeed = Random.Range(0.5f, 2f);
pointLightParams[k].lightRange = Random.Range(0.01f, 0.1f);
pointLightParams[k].moveSpeed = Random.value * 0.1f;
pointLightParams[k].accel = Vector3.zero;
pointLightParams[k].nextTargetInterval = 1f;
pointLightParams[k].randomTarget = true;
if (pointLightParams[k].randomTargetRadius == 0)
pointLightParams[k].randomTargetRadius = 0.4f;
SetNewLightTargetPos(k);
}
}
}
void SetNewLightTargetPos(int k) {
pointLightParams[k].targetPos = pointLightParams[k].randomTargetCenter + Random.insideUnitSphere * pointLightParams[k].randomTargetRadius;
pointLightParams[k].targetPos.x = Mathf.Clamp(pointLightParams[k].targetPos.x, -0.4f, 0.4f);
pointLightParams[k].targetPos.y = Mathf.Clamp(pointLightParams[k].targetPos.y, -0.4f, 0.4f);
pointLightParams[k].targetPos.z = Mathf.Clamp(pointLightParams[k].targetPos.z, -0.4f, 0.4f);
pointLightParams[k].nextTargetTime = Time.time + Random.value + pointLightParams[k].nextTargetInterval;
}
void UpdateLightEffects() {
#if UNITY_EDITOR
if (lastPointLightCount != pointLightParams.Length) {
lastPointLightCount = pointLightParams.Length;
}
for (int k = 0; k < pointLightParams.Length; k++) {
pointLightParams[k].name = "Light " + (k + 1);
}
#endif
// Animate lights
for (int k = 0; k < pointLightParams.Length; k++) {
Vector3 curPos = pointLightParams[k].localPosition;
if (pointLightParams[k].randomTarget && Time.time > pointLightParams[k].nextTargetTime) {
SetNewLightTargetPos(k);
}
Vector3 targetPos = pointLightParams[k].targetPos;
Vector3 accel = pointLightParams[k].accel;
float speed = pointLightParams[k].moveSpeed * Time.deltaTime;
accel.x += curPos.x < targetPos.x ? speed : -speed;
const float maxAccel = 5f;
if (accel.x > speed * maxAccel)
accel.x = speed * maxAccel;
else if (accel.x < speed * -maxAccel) {
accel.x = speed * -maxAccel;
}
accel.y += curPos.y < targetPos.y ? speed : -speed;
if (accel.y > speed * maxAccel)
accel.y = speed * maxAccel;
else if (accel.y < speed * -maxAccel) {
accel.y = speed * -maxAccel;
}
accel.z += curPos.z < targetPos.z ? speed : -speed;
if (accel.z > speed * maxAccel)
accel.z = speed * maxAccel;
else if (accel.z < speed * -maxAccel) {
accel.z = speed * -maxAccel;
}
pointLightParams[k].accel = accel;
curPos += pointLightParams[k].accel;
if (curPos.x < -0.5) {
curPos.x = -0.5f;
pointLightParams[k].accel.x = 0;
} else if (curPos.x > 0.5f) {
curPos.x = 0.5f;
pointLightParams[k].accel.x = 0;
}
if (curPos.y < -0.5) {
curPos.y = -0.5f;
pointLightParams[k].accel.y = 0;
} else if (curPos.y > 0.5f) {
curPos.y = 0.5f;
pointLightParams[k].accel.y = 0;
}
if (curPos.z < -0.5) {
curPos.z = -0.5f;
pointLightParams[k].accel.z = 0;
} else if (curPos.z > 0.5f) {
curPos.z = 0.5f;
pointLightParams[k].accel.z = 0;
}
pointLightParams[k].localPosition = curPos;
}
// Pass data to shader
if (pointLightColorBuffer == null || pointLightColorBuffer.Length != 6) {
pointLightColorBuffer = new Color[6];
}
if (pointLightPositionBuffer == null || pointLightPositionBuffer.Length != 6) {
pointLightPositionBuffer = new Vector4[6];
}
float range, multiplier;
for (int k = 0; k < 6; k++) {
range = multiplier = 0;
if (_pointLightsEnabled && k < pointLightParams.Length) {
Vector3 pos = transform.TransformPoint(pointLightParams[k].localPosition);
pointLightPositionBuffer[k] = pos;
range = pointLightParams[k].lightRange * pointLightsScatteringAmount / 25f; // note: 25 comes from Unity point light attenuation equation
float intensity = pointLightParams[k].intensityMin + Mathf.PingPong(Time.time * pointLightParams[k].intensitySpeed, pointLightParams[k].intensityMax - pointLightParams[k].intensityMin);
multiplier = pointLightsIntensity * intensity;
}
if (range > 0 && multiplier > 0) {
// Apply attenuation if light is affected by fog distance & falloff
Color pointLightColor = Color.Lerp(pointLightParams[k].color1, pointLightParams[k].color2, Mathf.PingPong(Time.time * pointLightParams[k].colorSpeed, 1f));
pointLightColorBuffer[k] = new Vector4(pointLightColor.r * multiplier, pointLightColor.g * multiplier, pointLightColor.b * multiplier, range);
} else {
pointLightColorBuffer[k] = Color.black;
}
}
if (liqMat != null) {
liqMat.SetFloat(ShaderParams.PointLightInsideAtten, pointLightInsideAtten);
liqMat.SetColorArray(ShaderParams.PointLightColorArray, pointLightColorBuffer);
liqMat.SetVectorArray(ShaderParams.PointLightPositionArray, pointLightPositionBuffer);
}
}
public void UpdateMaterialProperties() {
if (Application.isPlaying) {
shouldUpdateMaterialProperties = true;
} else {
UpdateMaterialPropertiesNow();
}
}
void UpdateMaterialPropertiesNow() {
if (!gameObject.activeInHierarchy)
return;
switch (_detail) {
case DETAIL.Simple:
case DETAIL.SimpleNoFlask:
if (liqMatSimple == null) {
liqMatSimple = Instantiate(Resources.Load("Materials/LiquidVolumeSimple")) as Material;
}
liqMat = liqMatSimple;
break;
case DETAIL.Multiple:
case DETAIL.MultipleNoFlask:
if (liqMatMultipleNoFlask == null) {
liqMatMultipleNoFlask = Instantiate(Resources.Load("Materials/LiquidVolumeMultipleNoFlask")) as Material;
}
liqMat = liqMatMultipleNoFlask;
break;
default:
if (liqMatDefaultNoFlask == null) {
liqMatDefaultNoFlask = Instantiate(Resources.Load("Materials/LiquidVolumeDefaultNoFlask")) as Material;
}
liqMat = liqMatDefaultNoFlask;
break;
}
if (_flaskMaterial == null) {
_flaskMaterial = Instantiate(Resources.Load("Materials/Flask"));
}
if (liqMat == null)
return;
CheckMeshDisplacement();
if (currentDetail != _detail) {
currentDetail = _detail;
requireLayersUpdate = true;
}
if (requireLayersUpdate && _detail.isMultiple()) {
UpdateLayersNow();
}
UpdateLevels();
if (mr == null)
return;
// Try to compute submesh index heuristically if this is the first time the liquid has been added to a multi-material mesh
mr.GetSharedMaterials(mrSharedMaterials);
int sharedMaterialsCount = mrSharedMaterials.Count;
if (_subMeshIndex < 0) {
for (int w = 0; w < defaultContainerNames.Length; w++) {
if (_subMeshIndex >= 0)
break;
for (int k = 0; k < sharedMaterialsCount; k++) {
if (mrSharedMaterials[k] != null && mrSharedMaterials[k] != _flaskMaterial && mrSharedMaterials[k].name.ToUpper().Contains(defaultContainerNames[w])) {
_subMeshIndex = k;
break;
}
}
}
}
if (_subMeshIndex < 0)
_subMeshIndex = 0;
if (sharedMaterialsCount > 1 && _subMeshIndex >= 0 && _subMeshIndex < sharedMaterialsCount) {
mrSharedMaterials[_subMeshIndex] = liqMat;
} else {
mrSharedMaterials.Clear();
mrSharedMaterials.Add(liqMat);
}
if (_flaskMaterial != null) {
bool shouldUseFlaskMaterial = _detail.usesFlask();
if (shouldUseFlaskMaterial && !mrSharedMaterials.Contains(_flaskMaterial)) {
// empty slot?
for (int k=0;k 0 ? 1000f : 0f);
}
liqMat.SetFloat(ShaderParams.SparklingIntensity, _sparklingIntensity * 250.0f);
liqMat.SetFloat(ShaderParams.SparklingThreshold, 1.0f - _sparklingAmount);
liqMat.SetFloat(ShaderParams.DepthAtten, _deepObscurance);
Color smokeColor = ApplyGlobalAlpha(_smokeColor);
int smokeRaySteps = _smokeRaySteps;
if (!_smokeEnabled) {
smokeColor.a = 0;
smokeRaySteps = 1;
}
liqMat.SetColor(ShaderParams.SmokeColor, smokeColor);
liqMat.SetFloat(ShaderParams.SmokeAtten, _smokeBaseObscurance);
liqMat.SetFloat(ShaderParams.SmokeHeightAtten, _smokeHeightAtten);
liqMat.SetFloat(ShaderParams.SmokeSpeed, _smokeSpeed);
liqMat.SetFloat(ShaderParams.SmokeRaySteps, smokeRaySteps);
liqMat.SetFloat(ShaderParams.LiquidRaySteps, _liquidRaySteps);
liqMat.SetColor(ShaderParams.FoamColor, ApplyGlobalAlpha(_foamColor));
liqMat.SetFloat(ShaderParams.FoamRaySteps, _foamThickness > 0 ? _foamRaySteps : 1);
liqMat.SetFloat(ShaderParams.FoamDensity, _foamThickness > 0 ? _foamDensity : -1f);
liqMat.SetFloat(ShaderParams.FoamWeight, _foamWeight);
liqMat.SetFloat(ShaderParams.FoamBottom, _foamVisibleFromBottom ? 1f : 0f);
liqMat.SetFloat(ShaderParams.FoamTurbulence, _foamTurbulence);
switch (_detail) {
case DETAIL.Multiple:
case DETAIL.MultipleNoFlask:
UpdateBubblesProperties();
break;
}
if (_bubblesAmount < 0) {
_bubblesAmount = 0;
}
if (_noiseVariation != currentNoiseVariation || requireBubblesUpdate) {
requireBubblesUpdate = false;
currentNoiseVariation = _noiseVariation;
if (noise3DTex == null || noise3DTex.Length != 4) {
noise3DTex = new Texture3D[4];
}
if (noise3DTex[currentNoiseVariation] == null) {
noise3DTex[currentNoiseVariation] = Resources.Load("Textures/Noise3D" + currentNoiseVariation.ToString());
}
Texture3D tex3d = noise3DTex[currentNoiseVariation];
if (tex3d != null) {
bool needsBubbleTextureUpdate = _bubblesAmount > 0 || (_bubblesAmount == 0 && tex3d != null && tex3d.name.Contains("Clone"));
if (needsBubbleTextureUpdate && _detail.isMultiple()) {
if (!tex3d.name.Contains("Clone")) {
tex3d = Instantiate(tex3d);
tex3d.hideFlags = HideFlags.DontSave;
noise3DTex[currentNoiseVariation] = tex3d;
}
if (colors3D == null || colors3D.Length != 4) {
colors3D = new Color[4][];
}
if (colors3D[currentNoiseVariation] == null) {
colors3D[currentNoiseVariation] = tex3d.GetPixels();
int length = colors3D[currentNoiseVariation].Length;
if (colors3DWithBubbles == null || colors3DWithBubbles.Length != 4) {
colors3DWithBubbles = new Color[4][];
}
if (colors3DWithBubbles[currentNoiseVariation] == null || colors3DWithBubbles[currentNoiseVariation].Length != length) {
colors3DWithBubbles[currentNoiseVariation] = new Color[length];
}
}
if (_bubblesAmount > 0 && _detail.isMultiple()) {
AddBubbles(tex3d);
} else {
ClearBubbles(tex3d);
}
}
liqMat.SetTexture(ShaderParams.NoiseTex, tex3d);
}
}
liqMat.renderQueue = _renderQueue;
UpdateInsideOut();
if (_topology == TOPOLOGY.Irregular) {
if (prevThickness != _flaskThickness) {
prevThickness = _flaskThickness;
}
}
}
Color ApplyGlobalAlpha(Color originalColor) {
return new Color(originalColor.r, originalColor.g, originalColor.b, originalColor.a * _alpha);
}
void GetRenderer() {
MeshFilter mf = GetComponent();
if (mf != null) {
mesh = mf.sharedMesh;
mr = GetComponent();
} else {
SkinnedMeshRenderer smr = GetComponent();
if (smr != null) {
mesh = smr.sharedMesh;
mr = smr;
}
}
}
void UpdateLevels(bool updateShaderKeywords = true) {
_level = Mathf.Clamp01(_level);
levelMultipled = _level * _levelMultiplier;
if (liqMat == null)
return;
if (mesh == null) {
GetRenderer();
ReadVertices();
} else if (mr == null) {
GetRenderer();
}
if (mesh == null || mr == null) {
return;
}
Vector4 size = new Vector4(mesh.bounds.extents.x * 2f * transform.lossyScale.x, mesh.bounds.extents.y * 2f * transform.lossyScale.y, mesh.bounds.extents.z * 2f * transform.lossyScale.z, 0);
size.x *= _extentsScale.x;
size.y *= _extentsScale.y;
size.z *= _extentsScale.z;
float maxWidth = Mathf.Max(size.x, size.z);
Vector3 extents = _ignoreGravity ? new Vector3(size.x * 0.5f, size.y * 0.5f, size.z * 0.5f) : mr.bounds.extents;
extents *= (1f - _flaskThickness);
extents.x *= _extentsScale.x;
extents.y *= _extentsScale.y;
extents.z *= _extentsScale.z;
// Compensate levelpos with upperlimit
float rotationAdjustment;
if (_upperLimit < 1f && !_ignoreGravity) {
float y1 = transform.TransformPoint(Vector3.up * extents.y).y;
float y0 = transform.TransformPoint(Vector3.up * (extents.y * _upperLimit)).y;
rotationAdjustment = Mathf.Max(y0 - y1, 0);
} else {
rotationAdjustment = 0;
}
#if DEBUG_SLICE
float approxVolume = 0;
#endif
// Compensate rotation in cylindrical shapes where mesh height is on another scale than width
float thisLevel = levelMultipled;
if (_rotationLevelCompensation != LEVEL_COMPENSATION.None && !_ignoreGravity && thisLevel > 0) {
MeshVolumeCalcFunction volumeFunction;
int exitIterations;
if (_rotationLevelCompensation == LEVEL_COMPENSATION.Fast) {
volumeFunction = GetMeshVolumeUnderLevelFast;
exitIterations = 8;
} else {
volumeFunction = GetMeshVolumeUnderLevel;
exitIterations = 10;
}
Vector3 mrCenter = mr.bounds.center;
if (lastLevelVolumeRef != thisLevel) {
lastLevelVolumeRef = thisLevel;
if (_topology == TOPOLOGY.Cylinder) {
float r = size.x * 0.5f;
float h = size.y * thisLevel;
volumeRef = Mathf.PI * r * r * h; // perfect cylinder volume (optimization for performance reasons, ideally should go through normal mesh volume computation - the else part)
} else {
Quaternion q = transform.rotation;
transform.rotation = Quaternion.identity;
float tempExtentY = _ignoreGravity ? size.y * 0.5f : mr.bounds.extents.y;
tempExtentY *= (1f - _flaskThickness);
tempExtentY *= _extentsScale.y;
RotateVertices();
volumeRef = volumeFunction(thisLevel, mrCenter, tempExtentY);
transform.rotation = q;
}
}
RotateVertices();
float nearestLevel = thisLevel;
float minVolumeDiff = float.MaxValue;
float maxLevel = Mathf.Clamp01(thisLevel + 0.5f);
float minLevel = Mathf.Clamp01(thisLevel - 0.5f);
for (int i = 0; i < 12; i++) {
thisLevel = (minLevel + maxLevel) * 0.5f;
float volume = volumeFunction(thisLevel, mrCenter, extents.y);
float volumeDiff = Mathf.Abs(volumeRef - volume);
if (volumeDiff < minVolumeDiff) {
#if DEBUG_SLICE
approxVolume = volume;
#endif
minVolumeDiff = volumeDiff;
nearestLevel = thisLevel;
}
if (volume < volumeRef) {
minLevel = thisLevel;
} else {
if (i >= exitIterations)
break;
maxLevel = thisLevel;
}
}
thisLevel = nearestLevel * _levelMultiplier;
} else {
if (levelMultipled <= 0)
thisLevel = -0.001f; // ensure it's below the flask thickness
}
liquidLevelPos = mr.bounds.center.y - extents.y;
liquidLevelPos += extents.y * 2f * thisLevel + rotationAdjustment;
#if DEBUG_SLICE
Debug.Log("Ref: " + volumeRef + " Vol: " + approxVolume + " Level: " + thisLevel);
#endif
liqMat.SetFloat(ShaderParams.LevelPos, liquidLevelPos);
float upperLimit = mesh.bounds.extents.y * _extentsScale.y * _upperLimit;
liqMat.SetFloat(ShaderParams.UpperLimit, upperLimit);
float lowerLimit = mesh.bounds.extents.y * _extentsScale.y * _lowerLimit;
liqMat.SetFloat(ShaderParams.LowerLimit, lowerLimit);
float visibleLevel = (levelMultipled <= 0 || levelMultipled >= 1f) ? 0f : 1f;
UpdateTurbulence();
float foamPos = mr.bounds.center.y - extents.y + (rotationAdjustment + extents.y * 2.0f * (thisLevel + _foamThickness)) * visibleLevel;
liqMat.SetFloat(ShaderParams.FoamMaxPos, foamPos);
Vector4 thickness = new Vector4(1.0f - _flaskThickness, (1.0f - _flaskThickness * maxWidth / size.z), (1.0f - _flaskThickness * maxWidth / size.z), 0);
liqMat.SetVector(ShaderParams.FlaskThickness, thickness);
size.w = size.x * 0.5f * thickness.x;
size.x = Vector3.Distance(mr.bounds.max, mr.bounds.min);
liqMat.SetVector(ShaderParams.Size, size);
float scaleFactor = size.y * 0.5f * (1.0f - _flaskThickness * maxWidth / size.y);
liqMat.SetVector(ShaderParams.Scale, new Vector4(_smokeScale / scaleFactor, _foamScale / scaleFactor, _liquidScale1 / scaleFactor, _liquidScale2 / scaleFactor));
liqMat.SetVector(ShaderParams.Center, transform.position);
if (shaderKeywords == null || shaderKeywords.Length != 6) {
shaderKeywords = new string[6];
}
for (int k = 0; k < shaderKeywords.Length; k++) {
shaderKeywords[k] = null;
}
if (_detail.isMultiple()) {
liqMat.SetFloat(ShaderParams.SizeWorld, mr.bounds.size.y);
}
if (_depthAware) {
shaderKeywords[SHADER_KEYWORD_DEPTH_AWARE_INDEX] = SHADER_KEYWORD_DEPTH_AWARE;
liqMat.SetFloat(ShaderParams.DepthAwareOffset, _depthAwareOffset);
}
if (_depthAwareCustomPass) {
shaderKeywords[SHADER_KEYWORD_DEPTH_AWARE_CUSTOM_PASS_INDEX] = SHADER_KEYWORD_DEPTH_AWARE_CUSTOM_PASS;
}
if (_reactToForces && _topology == TOPOLOGY.Sphere) {
shaderKeywords[SHADER_KEYWORD_IGNORE_GRAVITY_INDEX] = SHADER_KEYWORD_IGNORE_GRAVITY;
} else if (_ignoreGravity) {
shaderKeywords[SHADER_KEYWORD_IGNORE_GRAVITY_INDEX] = SHADER_KEYWORD_IGNORE_GRAVITY;
} else if (transform.rotation.eulerAngles != Vector3.zero) {
shaderKeywords[SHADER_KEYWORD_NON_AABB_INDEX] = SHADER_KEYWORD_NON_AABB;
}
switch (_topology) {
case TOPOLOGY.Sphere:
shaderKeywords[SHADER_KEYWORD_TOPOLOGY_INDEX] = SHADER_KEYWORD_SPHERE;
break;
case TOPOLOGY.Cube:
shaderKeywords[SHADER_KEYWORD_TOPOLOGY_INDEX] = SHADER_KEYWORD_CUBE;
break;
case TOPOLOGY.Cylinder:
shaderKeywords[SHADER_KEYWORD_TOPOLOGY_INDEX] = SHADER_KEYWORD_CYLINDER;
break;
default:
shaderKeywords[SHADER_KEYWORD_TOPOLOGY_INDEX] = SHADER_KEYWORD_IRREGULAR;
break;
}
if (_refractionBlur && _detail.allowsRefraction()) {
liqMat.SetFloat(ShaderParams.FlaskBlurIntensity, _blurIntensity * (_refractionBlur ? 1f : 0f));
shaderKeywords[SHADER_KEYWORD_REFRACTION_INDEX] = SHADER_KEYWORD_USE_REFRACTION;
}
if (updateShaderKeywords) {
liqMat.shaderKeywords = shaderKeywords;
}
lastPosition = transform.position;
lastScale = transform.localScale;
lastRotation = transform.rotation;
}
void RotateVertices() {
int vertexCount = verticesUnsorted.Length;
if (rotatedVertices == null || rotatedVertices.Length != vertexCount) {
rotatedVertices = new Vector3[vertexCount];
}
for (int k = 0; k < vertexCount; k++) {
rotatedVertices[k] = transform.TransformPoint(verticesUnsorted[k]);
}
}
float SignedVolumeOfTriangle(Vector3 p1, Vector3 p2, Vector3 p3, Vector3 zeroPoint) {
// zero point can be useful to avoid floating point issues if mesh is at very distant positions but in general this can be omitted
p1.x -= zeroPoint.x; p1.y -= zeroPoint.y; p1.z -= zeroPoint.z;
p2.x -= zeroPoint.x; p2.y -= zeroPoint.y; p2.z -= zeroPoint.z;
p3.x -= zeroPoint.x; p3.y -= zeroPoint.y; p3.z -= zeroPoint.z;
float v321 = p3.x * p2.y * p1.z;
float v231 = p2.x * p3.y * p1.z;
float v312 = p3.x * p1.y * p2.z;
float v132 = p1.x * p3.y * p2.z;
float v213 = p2.x * p1.y * p3.z;
float v123 = p1.x * p2.y * p3.z;
float e = (1.0f / 6.0f) * (-v321 + v231 + v312 - v132 - v213 + v123);
return e;
}
delegate float MeshVolumeCalcFunction(float level01, Vector3 zeroPoint, float yExtent);
float GetMeshVolumeUnderLevelFast(float level01, Vector3 zeroPoint, float yExtent) {
float level = mr.bounds.center.y - yExtent;
level += yExtent * 2f * level01;
float vol = 0;
for (int k = 0; k < verticesIndices.Length; k += 3) {
Vector3 p1 = rotatedVertices[verticesIndices[k]];
Vector3 p2 = rotatedVertices[verticesIndices[k + 1]];
Vector3 p3 = rotatedVertices[verticesIndices[k + 2]];
if (p1.y > level) p1.y = level;
if (p2.y > level) p2.y = level;
if (p3.y > level) p3.y = level;
vol += SignedVolumeOfTriangle(p1, p2, p3, zeroPoint);
}
return Mathf.Abs(vol);
}
Vector3 ClampVertexToSlicePlane(Vector3 p, Vector3 q, float level) {
Vector3 qp = (q - p).normalized;
float h = p.y - level;
return p + qp * h / -qp.y;
}
#if DEBUG_SLICE
GameObject o;
#endif
readonly List verts = new List();
readonly List cutPoints = new List();
Vector3 cutPlaneCenter;
float GetMeshVolumeUnderLevel(float level01, Vector3 zeroPoint, float yExtent) {
float level = mr.bounds.center.y - yExtent;
level += yExtent * 2f * level01;
cutPlaneCenter = Vector3.zero;
cutPoints.Clear();
verts.Clear();
// Slice mesh
int indicesCount = verticesIndices.Length;
for (int k = 0; k < indicesCount; k += 3) {
Vector3 p1 = rotatedVertices[verticesIndices[k]];
Vector3 p2 = rotatedVertices[verticesIndices[k + 1]];
Vector3 p3 = rotatedVertices[verticesIndices[k + 2]];
if (p1.y > level && p2.y > level && p3.y > level) continue;
if (p1.y < level && p2.y > level && p3.y > level) {
p2 = ClampVertexToSlicePlane(p2, p1, level);
p3 = ClampVertexToSlicePlane(p3, p1, level);
cutPoints.Add(p2);
cutPoints.Add(p3);
cutPlaneCenter += p2;
cutPlaneCenter += p3;
} else if (p2.y < level && p1.y > level && p3.y > level) {
p1 = ClampVertexToSlicePlane(p1, p2, level);
p3 = ClampVertexToSlicePlane(p3, p2, level);
cutPoints.Add(p1);
cutPoints.Add(p3);
cutPlaneCenter += p1;
cutPlaneCenter += p3;
} else if (p3.y < level && p1.y > level && p2.y > level) {
p1 = ClampVertexToSlicePlane(p1, p3, level);
p2 = ClampVertexToSlicePlane(p2, p3, level);
cutPoints.Add(p1);
cutPoints.Add(p2);
cutPlaneCenter += p1;
cutPlaneCenter += p2;
} else if (p1.y > level && p2.y < level && p3.y < level) {
Vector3 p12 = ClampVertexToSlicePlane(p1, p2, level);
Vector3 p13 = ClampVertexToSlicePlane(p1, p3, level);
verts.Add(p12);
verts.Add(p2);
verts.Add(p3);
verts.Add(p13);
verts.Add(p12);
verts.Add(p3);
cutPoints.Add(p12);
cutPoints.Add(p13);
cutPlaneCenter += p12;
cutPlaneCenter += p13;
continue;
} else if (p2.y > level && p1.y < level && p3.y < level) {
Vector3 p21 = ClampVertexToSlicePlane(p2, p1, level);
Vector3 p23 = ClampVertexToSlicePlane(p2, p3, level);
verts.Add(p1);
verts.Add(p21);
verts.Add(p3);
verts.Add(p21);
verts.Add(p23);
verts.Add(p3);
cutPoints.Add(p21);
cutPoints.Add(p23);
cutPlaneCenter += p21;
cutPlaneCenter += p23;
continue;
} else if (p3.y > level && p1.y < level && p2.y < level) {
Vector3 p31 = ClampVertexToSlicePlane(p3, p1, level);
Vector3 p32 = ClampVertexToSlicePlane(p3, p2, level);
verts.Add(p31);
verts.Add(p1);
verts.Add(p2);
verts.Add(p32);
verts.Add(p31);
verts.Add(p2);
cutPoints.Add(p31);
cutPoints.Add(p32);
cutPlaneCenter += p31;
cutPlaneCenter += p32;
continue;
}
verts.Add(p1);
verts.Add(p2);
verts.Add(p3);
}
// close sliced mesh
int cutPointsCount = cutPoints.Count;
if (cutPoints.Count >= 3) {
cutPlaneCenter /= cutPointsCount;
cutPoints.Sort(PolygonSortOnPlane);
for (int k = 0; k < cutPointsCount; k++) {
Vector3 p1 = cutPoints[k];
Vector3 p2;
if (k == cutPointsCount - 1) {
p2 = cutPoints[0];
} else {
p2 = cutPoints[k + 1];
}
verts.Add(cutPlaneCenter);
verts.Add(p1);
verts.Add(p2);
}
}
// compute mesh volume
int vertCount = verts.Count;
float vol = 0;
for (int k = 0; k < vertCount; k += 3) {
vol += SignedVolumeOfTriangle(verts[k], verts[k + 1], verts[k + 2], zeroPoint);
}
#if DEBUG_SLICE
if (o == null) {
o = new GameObject("Sliced", typeof(MeshFilter), typeof(MeshRenderer));
}
Mesh mesh = new Mesh();
mesh.vertices = verts.ToArray();
int len = mesh.vertices.Length;
int[] inds = new int[len];
for (int k = 0; k < inds.Length; k++) {
inds[k] = k;
}
mesh.triangles = inds;
MeshFilter mf = o.GetComponent();
mf.mesh = mesh;
#endif
return Mathf.Abs(vol);
}
int PolygonSortOnPlane(Vector3 p1, Vector3 p2) {
float r1 = Mathf.Atan2(p1.x - cutPlaneCenter.x, p1.z - cutPlaneCenter.z);
float r2 = Mathf.Atan2(p2.x - cutPlaneCenter.x, p2.z - cutPlaneCenter.z);
if (r1 < r2) return -1;
if (r1 > r2) return 1;
return 0;
}
void UpdateTurbulence() {
if (liqMat == null)
return;
float visibleLevel = levelMultipled > 0 ? 1f: 0f; // (_level<=0 || _level>=1f) ? 0.1f: 1f; // commented out to allow animation even level is 0 or full
float isInsideContainer = (camInside && _allowViewFromInside) ? 0f : 1f;
turb.x = _turbulence1 * visibleLevel * isInsideContainer;
turb.y = Mathf.Max(_turbulence2, turbulenceDueForces) * visibleLevel * isInsideContainer;
shaderTurb = turb;
shaderTurb.z *= 3.1415927f * _frecuency * 4f;
shaderTurb.w *= 3.1415927f * _frecuency * 4f;
liqMat.SetVector(ShaderParams.Turbulence, shaderTurb);
}
void CheckInsideOut() {
Camera cam = Camera.current;
if (cam == null || mr == null) {
if (!_allowViewFromInside)
UpdateInsideOut();
return;
}
Vector3 currentCamPos = cam.transform.position + cam.transform.forward * cam.nearClipPlane;
float currentDistanceToCam = (currentCamPos - transform.position).sqrMagnitude;
if (currentDistanceToCam == lastDistanceToCam)
return;
lastDistanceToCam = currentDistanceToCam;
// Check if position is inside container
bool nowInside = false;
switch (_topology) {
case TOPOLOGY.Cube:
nowInside = PointInAABB(currentCamPos);
break;
case TOPOLOGY.Cylinder:
nowInside = PointInCylinder(currentCamPos);
break;
default:
float diam = mesh.bounds.extents.x * 2f;
nowInside = (currentCamPos - transform.position).sqrMagnitude < (diam * diam);
break;
}
if (nowInside != camInside) {
camInside = nowInside;
UpdateInsideOut();
}
}
bool PointInAABB(Vector3 point) {
point = transform.InverseTransformPoint(point);
Vector3 ext = mesh.bounds.extents;
if (point.x < ext.x && point.x > -ext.x &&
point.y < ext.y && point.y > -ext.y &&
point.z < ext.z && point.z > -ext.z) {
return true;
} else {
return false;
}
}
bool PointInCylinder(Vector3 point) {
point = transform.InverseTransformPoint(point);
Vector3 ext = mesh.bounds.extents;
if (point.x < ext.x && point.x > -ext.x &&
point.y < ext.y && point.y > -ext.y &&
point.z < ext.z && point.z > -ext.z) {
point.y = 0;
Vector3 currentPos = transform.position;
currentPos.y = 0;
return (point - currentPos).sqrMagnitude < ext.x * ext.x;
}
return false;
}
void UpdateInsideOut() {
if (liqMat == null)
return;
if (_allowViewFromInside && camInside) {
liqMat.SetInt(ShaderParams.CullMode, (int)CullMode.Front);
liqMat.SetInt(ShaderParams.ZTestMode, (int)CompareFunction.Always);
if (_flaskMaterial != null) {
_flaskMaterial.SetInt(ShaderParams.CullMode, (int)CullMode.Front);
_flaskMaterial.SetInt(ShaderParams.ZTestMode, (int)CompareFunction.Always);
}
} else {
liqMat.SetInt(ShaderParams.CullMode, (int)CullMode.Back);
liqMat.SetInt(ShaderParams.ZTestMode, (int)CompareFunction.LessEqual);
if (_flaskMaterial != null) {
_flaskMaterial.SetInt(ShaderParams.CullMode, (int)CullMode.Back);
_flaskMaterial.SetInt(ShaderParams.ZTestMode, (int)CompareFunction.LessEqual);
}
}
UpdateTurbulence();
}
#endregion
#region Public API
///
/// Returns the vertical position in world space coordinates of the liquid surface
///
/// The get liquid surface Y position.
public float liquidSurfaceYPosition {
get {
return liquidLevelPos;
}
}
///
/// Changes the transform position and/or rotation of a floating object.
///
public void MoveToLiquidSurface(Transform transform, BuoyancyEffect effect, Transform root = null, float dampen = 0.8f) {
Vector3 position = transform.position;
if (effect == BuoyancyEffect.Simple) {
position.y = liquidLevelPos;
transform.position = position;
return;
}
if (effect == BuoyancyEffect.PositionOnly) {
Vector4 shaderTurb = liqMat.GetVector(ShaderParams.Turbulence);
float sizeY = liqMat.GetVector(ShaderParams.Size).y;
float turbFactor = shaderTurb.y * 0.05f * sizeY * _foamTurbulence;
float turbulence1 = Mathf.Sin(transform.position.x * shaderTurb.z + transform.position.z * shaderTurb.w + turbulenceSpeed * 4f) * turbFactor;
float y1 = liquidLevelPos + turbulence1;
position.y = y1;
transform.position = position;
return;
}
// Position and rotation
if (effect == BuoyancyEffect.PositionAndRotation) {
if (root == null) {
Debug.LogError("No root specified when calling MoveToLiquidSurface(..). Usually the root is a parent object that receives the buoyancy position and rotation changes.");
}
Vector4 shaderTurb = liqMat.GetVector(ShaderParams.Turbulence);
Collider collider = transform.GetComponent();
if (collider == null) {
Debug.LogError("No collider found in object used when calling MoveToLiquidSurface(..) of Liquid Volume.");
return;
}
float sizeY = liqMat.GetVector(ShaderParams.Size).y;
float turbFactor = shaderTurb.y * 0.05f * sizeY * _foamTurbulence;
Vector3 min = collider.bounds.min;
Vector3 max = collider.bounds.max;
float turbulence1 = Mathf.Sin(min.x * shaderTurb.z + min.z * shaderTurb.w + turbulenceSpeed * 4f) * turbFactor;
float turbulence2 = Mathf.Sin(max.x * shaderTurb.z + max.z * shaderTurb.w + turbulenceSpeed * 4f) * turbFactor;
float y1 = liquidLevelPos + turbulence1;
float y2 = liquidLevelPos + turbulence2;
position.y = (y1 + y2) * 0.5f;
root.transform.position = position;
Vector3 boundsCenter = collider.bounds.center;
Vector3 p1 = new Vector3(min.x - boundsCenter.x, y1, min.z - boundsCenter.z);
Vector3 p2 = new Vector3(max.x - boundsCenter.x, y2, max.z - boundsCenter.z);
Vector3 n = Vector3.Cross((p2 - p1).normalized, Vector3.back);
n.y = n.y < 0 ? -n.y : n.y;
Debug.DrawRay(transform.position, transform.position + n);
root.transform.up = Vector3.Lerp(root.transform.up, n, dampen);
}
}
///
/// Computes approximate point where liquid starts pouring over the flask when it's rotated
///
/// true, if spill point is detected, false otherwise.
/// Returned spill position in world space coordinates.
/// A value that determines where the aperture of the flask starts (0-1 where 0 is flask center and 1 is the very top).
public bool GetSpillPoint(out Vector3 spillPosition, float apertureStart = 1f) {
float spillAmount;
return GetSpillPoint(out spillPosition, out spillAmount, apertureStart);
}
///
/// Computes approximate point where liquid starts pouring over the flask when it's rotated
///
/// true, if spill point is detected, false otherwise.
/// Returned spill position in world space coordinates.
/// A returned value that represent the amount of liquid spilt.
/// A value that determines where the aperture of the flask starts (0-1 where 0 is flask center and 1 is the very top).
public bool GetSpillPoint(out Vector3 spillPosition, out float spillAmount, float apertureStart = 1f) {
spillPosition = Vector3.zero;
spillAmount = 0;
if (mesh == null || verticesSorted == null || levelMultipled <= 0)
return false;
float maxy = float.MinValue;
for (int k = 0; k < verticesSorted.Length; k++) {
Vector3 vertex = verticesSorted[k];
if (vertex.y > maxy) {
maxy = vertex.y;
}
}
float clampy = maxy * apertureStart * 0.99f;
Vector3 vt = transform.position;
bool crossed = false;
float miny = float.MaxValue;
for (int k = 0; k < verticesSorted.Length; k++) {
Vector3 vertex = verticesSorted[k];
if (vertex.y < clampy)
break;
vertex = transform.TransformPoint(vertex);
if (vertex.y < liquidLevelPos && vertex.y < miny) {
miny = vertex.y;
vt = vertex;
crossed = true;
}
}
if (!crossed)
return false;
spillPosition = vt;
spillAmount = (liquidLevelPos - vt.y) / (mesh.bounds.extents.y * 2f * transform.localScale.y);
return true;
}
void UpdateSpillPointGizmo() {
if (!_debugSpillPoint) {
if (spillPointGizmo != null) {
DestroyImmediate(spillPointGizmo.gameObject);
spillPointGizmo = null;
}
return;
}
if (spillPointGizmo == null) {
Transform t = transform.Find(SPILL_POINT_GIZMO);
if (t != null) {
DestroyImmediate(t.gameObject);
}
spillPointGizmo = GameObject.CreatePrimitive(PrimitiveType.Sphere);
spillPointGizmo.name = SPILL_POINT_GIZMO;
spillPointGizmo.transform.SetParent(transform, true);
Collider collider = spillPointGizmo.GetComponent();
if (collider != null)
DestroyImmediate(collider);
MeshRenderer mr = spillPointGizmo.GetComponent();
if (mr != null) {
mr.sharedMaterial = Instantiate(mr.sharedMaterial); // to avoid Editor (non playing) warning
mr.sharedMaterial.hideFlags = HideFlags.DontSave;
mr.sharedMaterial.color = Color.yellow;
}
}
Vector3 spillPoint;
if (GetSpillPoint(out spillPoint, 1f)) {
spillPointGizmo.transform.position = spillPoint;
if (mesh != null) {
Vector3 size = mesh.bounds.extents * 0.2f;
float s = size.x > size.y ? size.x : size.z;
s = s > size.z ? s : size.z;
spillPointGizmo.transform.localScale = new Vector3(s, s, s);
} else {
spillPointGizmo.transform.localScale = new Vector3(0.05f, 0.05f, 0.05f);
}
spillPointGizmo.SetActive(true);
} else {
spillPointGizmo.SetActive(false);
}
}
///
/// Applies current transform rotation and scale to the vertices and resets the transform rotation and scale to default values.
/// This operation makes the game object transform point upright as normal game objects and is required for Liquid Volume to work on imported models that comes with a rotation
///
public void BakeRotation() {
if (transform.localRotation == transform.rotation) {
// nothing to do!
return;
}
MeshFilter mf = GetComponent();
Mesh mesh = mf.sharedMesh;
if (mesh == null) return;
mesh = Instantiate(mesh);
Vector3[] vertices = mesh.vertices;
Vector3 scale = transform.localScale;
Vector3 localPos = transform.localPosition;
transform.localScale = Vector3.one;
Transform parent = transform.parent;
if (parent != null) {
transform.SetParent(null, false);
}
for (int k = 0; k < vertices.Length; k++) {
vertices[k] = transform.TransformVector(vertices[k]);
}
mesh.vertices = vertices;
mesh.RecalculateBounds();
mesh.RecalculateNormals();
mf.sharedMesh = mesh;
// Ensure parent has no different rotation
if (parent != null) {
transform.SetParent(parent, false);
transform.localPosition = localPos;
}
transform.localRotation = Quaternion.Euler(0, 0, 0);
transform.localScale = scale;
RefreshMeshAndCollider();
}
///
/// This operation computes the geometric center of all vertices and displaces them so the pivot is centered in the model
///
public void CenterPivot() {
CenterPivot(Vector3.zero, false);
}
readonly List hullVertices = new List();
[SerializeField]
Mesh fixedMesh, closedMesh;
///
/// This operation computes the geometric center of all vertices and displaces them so the pivot is centered in the model
///
public void CenterPivot(Vector3 offset, bool closeMesh) {
MeshFilter mf = GetComponent();
Mesh mesh = mf.sharedMesh;
if (mesh == null) return;
mesh = Instantiate(mesh);
mesh.name = mf.sharedMesh.name; // keep original name to detect if user assigns a different mesh to meshfilter and discard originalMesh reference
Vector3[] vertices = mesh.vertices;
if (closeMesh) {
hullVertices.Clear();
for (int k = 0; k < vertices.Length; k++) {
hullVertices.Add(new Vertex(vertices[k]));
}
// Create hull
var result = ConvexHull.Create(hullVertices);
mesh.Clear();
mesh.vertices = result.Points.Select(x => x.ToVec()).ToArray();
var xxx = result.Points.ToList();
List triangles = new List();
foreach (var face in result.Faces) {
triangles.Add(xxx.IndexOf(face.Vertices[0]));
triangles.Add(xxx.IndexOf(face.Vertices[1]));
triangles.Add(xxx.IndexOf(face.Vertices[2]));
}
mesh.SetTriangles(triangles, 0);
mesh.RecalculateBounds();
vertices = mesh.vertices;
closedMesh = mesh;
}
Vector3 midPoint = Vector3.zero;
for (int k = 0; k < vertices.Length; k++) {
midPoint += vertices[k];
}
midPoint /= vertices.Length;
midPoint += offset;
for (int k = 0; k < vertices.Length; k++) {
vertices[k] -= midPoint;
}
mesh.vertices = vertices;
mesh.RecalculateBounds();
if (closeMesh) {
mesh.RecalculateNormals();
}
mf.sharedMesh = mesh;
fixedMesh = mesh;
Vector3 localScale = transform.localScale;
midPoint.x *= localScale.x;
midPoint.y *= localScale.y;
midPoint.z *= localScale.z;
transform.localPosition += midPoint;
RefreshMeshAndCollider();
}
public void RefreshMeshAndCollider() {
ClearMeshCache();
MeshCollider mc = GetComponent();
if (mc != null) {
Mesh oldMesh = mc.sharedMesh;
mc.sharedMesh = null;
mc.sharedMesh = oldMesh;
}
}
///
/// Assigns a new layer density and optionally keep the same volume
///
/// New density.
/// If set to true keep volume.
public void SetLayerDensity(int layerIndex, float newDensity, bool keepVolume) {
if (layerIndex < 0 || _liquidLayers == null || layerIndex >= _liquidLayers.Length)
return;
float amount = _liquidLayers[layerIndex].amount;
if (keepVolume && _liquidLayers[layerIndex].density > 0) {
amount *= newDensity / _liquidLayers[layerIndex].density;
}
_liquidLayers[layerIndex].amount = amount;
_liquidLayers[layerIndex].density = newDensity;
}
#endregion
#region Multiple layers
void InitLayers() {
requireLayersUpdate = true;
lastLayerCount = -1;
if (_liquidLayers == null)
return;
for (int k = 0; k < _liquidLayers.Length; k++) {
_liquidLayers[k].ResetCurrentStates();
}
}
public void UpdateLayers(bool immediate = false) {
shouldUpdateMaterialProperties = true;
requireLayersUpdate = true;
lastLayerCount = _liquidLayers.Length;
if (immediate) {
this.immediate = true;
RenderObject();
}
}
void UpdateLayersNow() {
requireLayersUpdate = false;
if (_liquidLayers == null) {
_liquidLayers = new LiquidLayer[0];
} else if (_liquidLayers.Length > MAX_LAYERS) {
Array.Resize(ref _liquidLayers, MAX_LAYERS);
Debug.Log("Liquid layers max count exceeded. Capping to " + MAX_LAYERS + ".");
}
if (lastLayerCount >= 0 && lastLayerCount < _liquidLayers.Length) {
for (int k = lastLayerCount; k < _liquidLayers.Length; k++) {
_liquidLayers[k].SetDefaults(k);
}
}
lastLayerCount = _liquidLayers.Length;
if (sortedLayers == null || sortedLayers.Length < MAX_LAYERS) {
sortedLayers = new int[MAX_LAYERS];
}
// ensure proper values
for (int k = 0; k < _liquidLayers.Length; k++) {
_liquidLayers[k].amount = Mathf.Max(0, _liquidLayers[k].amount);
_liquidLayers[k].scale = Mathf.Clamp(_liquidLayers[k].scale, 0.001f, 0.48f);
_liquidLayers[k].murkiness = Mathf.Clamp01(_liquidLayers[k].murkiness);
if (_liquidLayers[k].adjustmentSpeed <= 0)
_liquidLayers[k].adjustmentSpeed = 1f;
if (_liquidLayers[k].density < 0.001f)
_liquidLayers[k].density = 0.001f;
}
// Sort & stack by density
sortedLayersCount = 0;
float currentDensity = float.MaxValue;
float baseLevel = 0;
for (int k = 0; k < _liquidLayers.Length; k++) {
float maxDensity = float.MinValue;
// Pick greater density first
for (int j = 0; j < _liquidLayers.Length; j++) {
float density = _liquidLayers[j].density;
if (density > maxDensity && density < currentDensity) {
maxDensity = density;
}
}
if (maxDensity == float.MinValue)
break;
// Place layers of the mix
for (int j = 0; j < _liquidLayers.Length; j++) {
if (_liquidLayers[j].density == maxDensity) {
_liquidLayers[j].baseLevel = baseLevel;
sortedLayers[sortedLayersCount++] = j;
baseLevel += _liquidLayers[j].amount / _liquidLayers[j].density;
}
}
currentDensity = maxDensity;
}
// Handle miscible layers
for (int k = 0; k < sortedLayersCount; k++) {
int i = sortedLayers[k];
if (_liquidLayers[i].miscible) {
LiquidLayer temp = new LiquidLayer();
int j = k;
float groupBaseLevel = _liquidLayers[i].baseLevel;
float groupDensity = _liquidLayers[i].density;
while (_liquidLayers[i].miscible && _liquidLayers[i].density == groupDensity) {
float amount = Mathf.Max(_liquidLayers[i].amount, 0.0001f);
temp.color += _liquidLayers[i].color * amount;
temp.murkColor += _liquidLayers[i].murkColor * amount;
temp.murkiness += _liquidLayers[i].murkiness * amount;
temp.scale += _liquidLayers[i].scale * amount;
temp.amount += amount;
if (++j >= sortedLayersCount)
break;
i = sortedLayers[j];
}
// Combine
for (int l = k; l < j; l++) {
i = sortedLayers[l];
_liquidLayers[i].mixedColor = temp.color / temp.amount;
_liquidLayers[i].mixedColor2 = temp.murkColor / temp.amount;
_liquidLayers[i].mixedMurkiness = temp.murkiness / temp.amount;
_liquidLayers[i].mixedScale = temp.scale / temp.amount;
_liquidLayers[i].mixedBubblesOpacity = temp.bubblesOpacity / temp.amount;
_liquidLayers[i].mixedAmount = temp.amount / groupDensity;
_liquidLayers[i].baseLevel = groupBaseLevel;
}
k = j - 1;
} else {
_liquidLayers[i].mixedColor = _liquidLayers[i].color;
_liquidLayers[i].mixedColor2 = _liquidLayers[i].murkColor;
_liquidLayers[i].mixedMurkiness = _liquidLayers[i].murkiness;
_liquidLayers[i].mixedScale = _liquidLayers[i].scale;
_liquidLayers[i].mixedBubblesOpacity = _liquidLayers[i].bubblesOpacity;
_liquidLayers[i].mixedAmount = Mathf.Clamp01(_liquidLayers[i].amount / _liquidLayers[i].density);
}
}
// Compact
for (int k = 0; k < sortedLayersCount - 1; k++) {
int i = sortedLayers[k];
int j = sortedLayers[k + 1];
float expectedBaseLevel = _liquidLayers[i].baseLevel + _liquidLayers[i].mixedAmount;
if (_liquidLayers[j].baseLevel > expectedBaseLevel) {
_liquidLayers[j].baseLevel = expectedBaseLevel;
}
}
AnimateLayers(true);
}
int layerComparer(LiquidLayer layer1, LiquidLayer layer2) {
return layer1.density.CompareTo(layer2.density);
}
void AnimateLayers(bool changes = false) {
if (Application.isPlaying) {
for (int j = 0; j < sortedLayersCount; j++) {
int k = sortedLayers[j];
if (_liquidLayers[k].currentBaseLevel != _liquidLayers[k].baseLevel || _liquidLayers[k].currentAmount != _liquidLayers[k].mixedAmount || _liquidLayers[k].currentMurkiness != _liquidLayers[k].mixedMurkiness || _liquidLayers[k].currentBubblesOpacity != _liquidLayers[k].mixedBubblesOpacity || _liquidLayers[k].currentColor != _liquidLayers[k].mixedColor || _liquidLayers[k].currentColor2 != _liquidLayers[k].mixedColor2) {
float t = Time.deltaTime * _layersAdjustmentSpeed * _liquidLayers[k].adjustmentSpeed;
if (t > 1f || immediate) {
t = 1f;
}
if (Similar(_liquidLayers[k].currentBaseLevel, _liquidLayers[k].baseLevel)) {
_liquidLayers[k].currentBaseLevel = _liquidLayers[k].baseLevel;
} else {
_liquidLayers[k].currentBaseLevel = Mathf.Lerp(_liquidLayers[k].currentBaseLevel, _liquidLayers[k].baseLevel, t);
}
if (Similar(_liquidLayers[k].currentAmount, _liquidLayers[k].mixedAmount)) {
_liquidLayers[k].currentAmount = _liquidLayers[k].mixedAmount;
} else {
_liquidLayers[k].currentAmount = Mathf.Lerp(_liquidLayers[k].currentAmount, _liquidLayers[k].mixedAmount, t);
}
if (Similar(_liquidLayers[k].currentMurkiness, _liquidLayers[k].mixedMurkiness)) {
_liquidLayers[k].currentMurkiness = _liquidLayers[k].mixedMurkiness;
} else {
_liquidLayers[k].currentMurkiness = Mathf.Lerp(_liquidLayers[k].currentMurkiness, _liquidLayers[k].mixedMurkiness, t);
}
if (Similar(ref _liquidLayers[k].currentColor, ref _liquidLayers[k].mixedColor)) {
_liquidLayers[k].currentColor = _liquidLayers[k].mixedColor;
} else {
_liquidLayers[k].currentColor = Color.Lerp(_liquidLayers[k].currentColor, _liquidLayers[k].mixedColor, t);
}
if (Similar(ref _liquidLayers[k].currentColor2, ref _liquidLayers[k].mixedColor2)) {
_liquidLayers[k].currentColor2 = _liquidLayers[k].mixedColor2;
} else {
_liquidLayers[k].currentColor2 = Color.Lerp(_liquidLayers[k].currentColor2, _liquidLayers[k].mixedColor2, t);
}
if (Similar(_liquidLayers[k].currentBubblesOpacity, _liquidLayers[k].mixedBubblesOpacity)) {
_liquidLayers[k].currentBubblesOpacity = _liquidLayers[k].mixedBubblesOpacity;
} else {
_liquidLayers[k].currentBubblesOpacity = Mathf.Lerp(_liquidLayers[k].currentBubblesOpacity, _liquidLayers[k].mixedBubblesOpacity, t);
}
changes = true;
}
}
if (changes) {
// Compact
if (_layersAdjustmentCompact) {
float currentBaseLevel = float.MinValue;
float expectedBaseLevel = 0;
for (int k = 0; k < _liquidLayers.Length; k++) {
// Get the minimum
float minCurrentBaseLevel = float.MaxValue;
int min = -1;
for (int j = 0; j < _liquidLayers.Length; j++) {
if (_liquidLayers[j].currentBaseLevel < minCurrentBaseLevel && _liquidLayers[j].currentBaseLevel > currentBaseLevel) {
minCurrentBaseLevel = _liquidLayers[j].currentBaseLevel;
min = j;
}
}
if (min < 0)
break;
if (k > 0) {
if (_liquidLayers[min].currentBaseLevel > expectedBaseLevel) {
_liquidLayers[min].currentBaseLevel = expectedBaseLevel;
}
}
expectedBaseLevel = _liquidLayers[min].currentBaseLevel + _liquidLayers[min].currentAmount;
currentBaseLevel = _liquidLayers[min].currentBaseLevel;
}
}
}
} else {
for (int j = 0; j < sortedLayersCount; j++) {
int k = sortedLayers[j];
if (_liquidLayers[k].currentBaseLevel != _liquidLayers[k].baseLevel || _liquidLayers[k].currentAmount != _liquidLayers[k].mixedAmount || _liquidLayers[k].currentMurkiness != _liquidLayers[k].mixedMurkiness || _liquidLayers[k].currentColor != _liquidLayers[k].mixedColor || _liquidLayers[k].currentColor2 != _liquidLayers[k].mixedColor2) {
_liquidLayers[k].currentBaseLevel = _liquidLayers[k].baseLevel;
_liquidLayers[k].currentAmount = _liquidLayers[k].mixedAmount;
_liquidLayers[k].currentColor = _liquidLayers[k].mixedColor;
_liquidLayers[k].currentColor2 = _liquidLayers[k].mixedColor2;
_liquidLayers[k].currentMurkiness = _liquidLayers[k].mixedMurkiness;
_liquidLayers[k].currentBubblesOpacity = _liquidLayers[k].mixedBubblesOpacity;
changes = true;
}
}
}
immediate = false;
if (changes) {
UpdateLayersProperties();
}
}
bool Similar(float a, float b) {
if (a < b) {
return (b - a) < 0.01f;
} else {
return (a - b) < 0.01f;
}
}
bool Similar(ref Color a, ref Color b) {
float dr, dg, db, da;
dr = a.r < b.r ? b.r - a.r : a.r - b.r;
if (dr > 0.01f)
return false;
dg = a.g < b.g ? b.g - a.g : a.g - b.g;
if (dg > 0.01f)
return false;
db = a.b < b.b ? b.b - a.b : a.b - b.b;
if (db > 0.01f)
return false;
da = a.a < b.a ? b.a - a.a : a.a - b.a;
return da < 0.01f;
}
void UpdateLayersProperties() {
if (_liquidLayers == null || sortedLayers == null || liqMat == null)
return;
const int LAYERS_RESOLUTION = 256;
if (layersTextureColors == null || layersTextureColors.Length != LAYERS_RESOLUTION) {
layersTextureColors = new Color[LAYERS_RESOLUTION];
}
if (layersTextureColors2 == null || layersTextureColors2.Length != LAYERS_RESOLUTION) {
layersTextureColors2 = new Color[LAYERS_RESOLUTION];
}
if (layersTextureColorsTemp == null || layersTextureColorsTemp.Length != LAYERS_RESOLUTION) {
layersTextureColorsTemp = new Color[LAYERS_RESOLUTION];
}
if (layersProperties == null || layersProperties.Length != LAYERS_RESOLUTION) {
layersProperties = new Vector4[LAYERS_RESOLUTION];
}
if (layersSum == null || layersSum.Length != LAYERS_RESOLUTION) {
layersSum = new float[LAYERS_RESOLUTION];
}
Color colorNull = new Color(0, 0, 0, 0);
Vector4 zero = Vector4.zero;
for (int j = 0; j < LAYERS_RESOLUTION; j++) {
layersTextureColors[j] = colorNull;
layersTextureColors2[j] = colorNull;
layersTextureColorsTemp[j] = colorNull;
layersProperties[j] = zero;
layersSum[j] = 0;
}
float level = 0;
for (int k = 0; k < sortedLayersCount; k++) {
float layerBaseLevel = _liquidLayers[k].currentBaseLevel;
float layerAmount = _liquidLayers[k].currentAmount;
if (layerAmount + layerBaseLevel > 1f) {
layerAmount = 1f - layerBaseLevel;
if (layerAmount < 0)
layerAmount = 0;
}
if (level < layerBaseLevel + layerAmount)
level = layerBaseLevel + layerAmount;
}
if (level != _level) {
_level = level;
UpdateLevels();
}
int y = 0, y_next = 0;
for (int i = 0; i < sortedLayersCount; i++) {
int k = sortedLayers[i];
Color layerColor = ApplyGlobalAlpha(_liquidLayers[k].currentColor);
Color layerColor2 = ApplyGlobalAlpha(_liquidLayers[k].currentColor2);
float alpha = layerColor.a;
layerColor *= alpha;
layerColor2 *= alpha;
float layerMurkiness = _liquidLayers[k].currentMurkiness * alpha;
float layerScale = _liquidLayers[k].mixedScale * 10.0f;
float layerBaseLevel = _liquidLayers[k].currentBaseLevel * _levelMultiplier;
float layerAmount = _liquidLayers[k].currentAmount * _levelMultiplier;
float layerTurbulence = _liquidLayers[k].viscosity * alpha;
float layerBubblesOpacity = _liquidLayers[k].bubblesOpacity * alpha;
y = (int)((levelMultipled - layerBaseLevel) * LAYERS_RESOLUTION);
y_next = (int)((levelMultipled - layerBaseLevel - layerAmount) * LAYERS_RESOLUTION);
for (int j = y_next; j < y; j++) {
if (j >= 0 && j < LAYERS_RESOLUTION) {
layersTextureColors[j] += layerColor;
layersTextureColors2[j] += layerColor2;
layersProperties[j].x += layerMurkiness;
layersProperties[j].y = layerScale > layersProperties[j].y ? layerScale : layersProperties[j].y;
layersProperties[j].z += layerTurbulence;
layersProperties[j].w += layerBubblesOpacity;
layersSum[j] += alpha;
}
}
}
int lastLayer = 0;
for (int j = 0; j < LAYERS_RESOLUTION; j++) {
float m = layersSum[j];
if (m > 0) {
layersTextureColors[j] /= m;
layersTextureColors2[j] /= m;
layersProperties[j].x /= m;
layersProperties[j].z /= m;
layersProperties[j].w /= m;
lastLayer = j;
}
}
// Copy last layer to fill gap when tilting liquid
if (!_ignoreGravity) {
for (int j = lastLayer + 1; j < LAYERS_RESOLUTION; j++) {
layersTextureColors[j] = layersTextureColors[lastLayer];
layersTextureColors2[j] = layersTextureColors2[lastLayer];
layersProperties[j] = layersProperties[lastLayer];
}
}
// Smooth contact surfaces
if (_smoothContactSurface > 0) {
for (int j = 0; j < LAYERS_RESOLUTION; j++) {
Color sum = colorNull;
int count = 0;
for (int k = j - _smoothContactSurface; k <= j + _smoothContactSurface; k++) {
if (k >= 0 && k < LAYERS_RESOLUTION) {
sum += layersTextureColors[k];
count++;
}
}
layersTextureColorsTemp[j] = sum / count;
}
// Blit colors back
for (int j = 0; j < LAYERS_RESOLUTION; j++) {
layersTextureColors[j] = layersTextureColorsTemp[j];
}
}
UploadMaterialArrays();
#if UNITY_WEBGL
SetLayersTextures();
#else
if (FORCE_GLES_COMPATIBILITY) {
SetLayersTextures();
}
#endif
}
void UploadMaterialArrays() {
liqMat.SetColorArray(ShaderParams.LayersColorArray, layersTextureColors);
liqMat.SetColorArray(ShaderParams.LayersColor2Array, layersTextureColors2);
liqMat.SetVectorArray(ShaderParams.LayersProperties, layersProperties);
}
void SetLayersTextures() {
if (layersPropertiesTex == null) {
layersPropertiesTex = new Texture2D(1, 256, TextureFormat.RGBAFloat, false, true);
layersPropertiesTex.filterMode = FilterMode.Point;
layersPropertiesTex.wrapMode = TextureWrapMode.Clamp;
}
if (layersColorsTex == null) {
layersColorsTex = new Texture2D(1, 256, TextureFormat.ARGB32, false);
layersColorsTex.filterMode = FilterMode.Point;
layersColorsTex.wrapMode = TextureWrapMode.Clamp;
}
if (layersPropertiesTexContents == null || layersPropertiesTexContents.Length < 256) {
layersPropertiesTexContents = new Color[256];
}
if (layersColorsTexContents == null || layersColorsTexContents.Length < 256) {
layersColorsTexContents = new Color[256];
}
if (layersColors2Tex == null) {
layersColors2Tex = new Texture2D(1, 256, TextureFormat.ARGB32, false);
layersColors2Tex.filterMode = FilterMode.Point;
layersColors2Tex.wrapMode = TextureWrapMode.Clamp;
}
if (layersColors2TexContents == null || layersColors2TexContents.Length < 256) {
layersColors2TexContents = new Color[256];
}
for (int k = 0; k < 256; k++) {
layersPropertiesTexContents[k].r = layersProperties[k].x;
layersPropertiesTexContents[k].g = layersProperties[k].y;
layersPropertiesTexContents[k].b = layersProperties[k].z;
layersPropertiesTexContents[k].a = layersProperties[k].w;
layersColorsTexContents[k] = layersTextureColors[k];
layersColors2TexContents[k] = layersTextureColors2[k];
}
layersPropertiesTex.SetPixels(layersPropertiesTexContents);
layersPropertiesTex.Apply();
liqMat.SetTexture(ShaderParams.LayersPropertiesTex, layersPropertiesTex);
layersColorsTex.SetPixels(layersColorsTexContents);
layersColorsTex.Apply();
liqMat.SetTexture(ShaderParams.LayersColorsTex, layersColorsTex);
layersColors2Tex.SetPixels(layersColors2TexContents);
layersColors2Tex.Apply();
liqMat.SetTexture(ShaderParams.LayersColors2Tex, layersColors2Tex);
}
public void Redraw() {
if (_liquidLayers != null) {
for (int k = 0; k < _liquidLayers.Length; k++) {
_liquidLayers[k].baseLevel = 0;
_liquidLayers[k].currentAmount = 0;
_liquidLayers[k].currentBaseLevel = 0;
}
requireLayersUpdate = true;
}
UpdateMaterialProperties();
}
#endregion
#region Mesh Displacement
void CheckMeshDisplacement() {
MeshFilter meshFilter = GetComponent();
if (meshFilter == null) {
originalMesh = null;
return;
}
Mesh currentMesh = meshFilter.sharedMesh;
if (currentMesh == null) {
if (_fixMesh) {
if (_autoCloseMesh && closedMesh != null) {
meshFilter.sharedMesh = closedMesh;
return;
} else if (!_autoCloseMesh && fixedMesh != null) {
meshFilter.sharedMesh = fixedMesh;
return;
} else if (originalMesh != null) {
meshFilter.sharedMesh = originalMesh;
}
} else {
originalMesh = null;
return;
}
currentMesh = meshFilter.sharedMesh;
}
if (!_fixMesh) {
RestoreOriginalMesh();
originalMesh = null;
return;
}
if (_autoCloseMesh && currentMesh == closedMesh) return;
if (!_autoCloseMesh && currentMesh == fixedMesh) return;
// Backup original mesh
if (originalMesh == null || !(originalMesh.name.Equals(currentMesh.name))) {
originalMesh = meshFilter.sharedMesh;
}
if (currentMesh != originalMesh) {
RestoreOriginalMesh();
}
Vector3 pos = transform.localPosition;
CenterPivot(_pivotOffset, _autoCloseMesh);
originalPivotOffset = transform.localPosition - pos;
}
void RestoreOriginalMesh() {
fixedMesh = closedMesh = null;
if (originalMesh == null) return;
MeshFilter meshFilter = GetComponent();
if (meshFilter == null) return;
meshFilter.sharedMesh = originalMesh;
transform.localPosition -= originalPivotOffset;
RefreshMeshAndCollider();
}
#endregion
#region Bubbles support
void AddBubbles(Texture3D tex) {
if (tex == null)
return;
if (_bubblesSizeMax < _bubblesSizeMin) {
_bubblesSizeMax = _bubblesSizeMin;
}
int size = tex.width;
Array.Copy(colors3D[currentNoiseVariation], colors3DWithBubbles[currentNoiseVariation], colors3DWithBubbles[currentNoiseVariation].Length);
Color[] colors = colors3DWithBubbles[currentNoiseVariation];
// Clear bubble buffer
for (int k = 0; k < colors.Length; k++) {
colors[k].g = 0.5f;
colors[k].b = 0.5f;
colors[k].a = 0.5f;
}
// Save current random state
Random.InitState(_bubblesSeed);
// Mark bubble positions
Vector3 nrm = Vector3.zero;
for (int k = 0; k < _bubblesAmount; k++) {
int bubbleSize = Random.Range(_bubblesSizeMin, _bubblesSizeMax + 1);
float bubbleSizeSqr = (float)(bubbleSize * bubbleSize);
int y = Random.Range(bubbleSize, size - bubbleSize);
int z = Random.Range(bubbleSize, size - bubbleSize);
int x = Random.Range(bubbleSize, size - bubbleSize);
for (int y2 = y - bubbleSize; y2 <= y + bubbleSize; y2++) {
float dy = y2 - y;
for (int z2 = z - bubbleSize; z2 <= z + bubbleSize; z2++) {
float dz = z2 - z;
for (int x2 = x - bubbleSize; x2 <= x + bubbleSize; x2++) {
float dx = x2 - x;
float dSqr = dy * dy + dz * dz + dx * dx;
float d = 1f - dSqr / bubbleSizeSqr;
if (d >= 0) {
float l = (float)System.Math.Sqrt(dSqr);
nrm.x = dx / l;
nrm.y = dy / l;
nrm.z = dz / l;
nrm.x = (nrm.x + 1f) * 0.5f;
nrm.y = (nrm.y + 1f) * 0.5f;
nrm.z = (nrm.z + 1f) * 0.5f;
int c = y2 * size * size + z2 * size + x2;
colors[c].g = nrm.x;
colors[c].b = nrm.y;
colors[c].a = nrm.z;
}
}
}
}
}
#if UNITY_WEBGL
bool useUnwrappedTexture = true;
#else
bool useUnwrappedTexture = FORCE_GLES_COMPATIBILITY;
#endif
if (useUnwrappedTexture) {
if (noise3DUnwrapped == null) {
noise3DUnwrapped = new Texture2D(size, size * size, TextureFormat.RGBA32, false, true);
}
noise3DUnwrapped.SetPixels(colors);
noise3DUnwrapped.Apply();
liqMat.SetTexture(ShaderParams.NoiseTexUnwrapped, noise3DUnwrapped);
}
// Push colors to texture
tex.SetPixels(colors);
tex.Apply();
}
void ClearBubbles(Texture3D tex) {
if (tex == null)
return;
Array.Copy(colors3D[currentNoiseVariation], colors3DWithBubbles[currentNoiseVariation], colors3DWithBubbles[currentNoiseVariation].Length);
Color[] colors = colors3DWithBubbles[currentNoiseVariation];
// Clear bubble buffer
for (int k = 0; k < colors.Length; k++) {
colors[k].g = 0.5f;
colors[k].b = 0.5f;
colors[k].a = 0.5f;
}
// Push colors to texture
tex.SetPixels(colors);
tex.Apply();
liqMat.SetTexture(ShaderParams.NoiseTexUnwrapped, null);
}
#endregion
}
}