/// <summary> /// Executes the simulation step. /// </summary> /// <param name="speed"> /// The overall simulation speed factor. /// </param> /// <param name="damping"> /// The damping value (range 0-1). /// </param> public override void StepSimulation(float speed, float damping) { if (!OnlyAmbient) { base.StepSimulation(speed, damping); } _canInteract = !OnlyAmbient; bool isFieldObstructionNull = _fieldObstruction == null; // Ambient wave time _time += Time.deltaTime * AmbientWaveSpeed; // Caching to avoid many divisions Vector2 invGrid = new Vector2(1f / _grid.x, 1f / _grid.y); for (int i = 0; i < _grid.x; i++) { for (int j = 0; j < _grid.y; j++) { int index = j * _grid.x + i; if (!isFieldObstructionNull && _fieldObstruction[index] == byte.MinValue) { continue; } // Obstruction value (0-1) determined by obstruction geometry and obstruction mask float obstructionValue = isFieldObstructionNull ? 1f : _fieldObstruction[index] * FastFunctions.InvertedByteMaxValue; // Normalizing the coordinates float normX = i * invGrid.x; float normY = j * invGrid.y; // Calculating new node value float val = (normX + normY) * FastFunctions.DoublePi; if (OnlyAmbient) { _fieldSum[index] = FastFunctions.FastSin(val * AmbientWaveFrequency + _time) * AmbientWaveHeight * obstructionValue; } else { _fieldSum[index] = FastFunctions.FastSin(val * AmbientWaveFrequency + _time) * AmbientWaveHeight * obstructionValue + FieldSimNew[index]; } } } // Updating the field _field = _fieldSum; _isDirty = true; }
/// <summary> /// Checks whether the point is inside a collider. /// </summary> /// <remarks> /// This method can check against all types of colliders, /// including <c>TerrainCollider</c> and concave <c>MeshCollider</c>. /// </remarks> /// <param name="collider"> /// The collider to check against. /// </param> /// <param name="point"> /// The point being checked. /// </param> /// <param name="specificTest"> /// Defines a kind of specific collision test that must be done against <paramref name="collider"/>. /// </param> /// <returns> /// <c>true</c> if the <paramref name="point"/> is inside the <paramref name="collider"/>, /// <c>false</c> otherwise. /// </returns> public static bool IsPointInsideCollider(Collider collider, Vector3 point, ColliderSpecificTestEnum specificTest = ColliderSpecificTestEnum.None) { RaycastHit hit; if (specificTest == ColliderSpecificTestEnum.None) { #if !UNITY_FLASH if (collider is TerrainCollider) { if (!collider.Raycast(new Ray(point, Vector3.up), out hit, collider.bounds.size.y)) { return(false); } } else #endif if (collider is MeshCollider && !((MeshCollider)collider).convex) { if (!IsPointInsideMeshCollider(collider, point)) { return(false); } } else { Vector3 direction = collider.bounds.center - point; float directionMagnitude = direction.sqrMagnitude; if (directionMagnitude > 0.0001f && collider.Raycast(new Ray(point, direction.normalized), out hit, FastFunctions.FastSqrt(directionMagnitude))) { return(false); } } } else { if (specificTest == ColliderSpecificTestEnum.Terrain) { if (!collider.Raycast(new Ray(point + Vector3.up * collider.bounds.size.y, Vector3.down), out hit, collider.bounds.size.y)) { return(false); } } } return(true); }
/// <summary> /// Executes the simulation step. /// </summary> /// <param name="speed"> /// The overall simulation speed factor. /// </param> /// <param name="damping"> /// The damping value (range 0-1). /// </param> public override void StepSimulation(float speed, float damping) { if (!OnlyAmbient) { base.StepSimulation(speed, damping); } _canInteract = !OnlyAmbient; bool isFieldObstructionNull = _fieldObstruction == null; // Ambient wave time _time += Time.deltaTime; // Recalculating direction foreach (Wave wave in Waves) { wave.Direction = new Vector2(FastFunctions.FastCos(wave.Angle * FastFunctions.Deg2Rad), FastFunctions.FastSin(wave.Angle * FastFunctions.Deg2Rad)); wave.CircleShift = new Vector2(-Mathf.Clamp01(wave.Position.x) * FastFunctions.DoublePi, -Mathf.Clamp01(wave.Position.y) * FastFunctions.DoublePi); } // Caching to avoid many divisions Vector2 invGrid = new Vector2(1f / _grid.x, 1f / _grid.y); for (int i = 0; i < _grid.x; i++) { for (int j = 0; j < _grid.y; j++) { int index = j * _grid.x + i; if (!isFieldObstructionNull && _fieldObstruction[index] == byte.MinValue) { continue; } // Obstruction value (0-1) determined by obstruction geometry and obstruction mask float obstructionValue = isFieldObstructionNull ? 1f : _fieldObstruction[index] * FastFunctions.InvertedByteMaxValue; // Normalizing the coordinates float normX = i * invGrid.x * FastFunctions.DoublePi; float normY = j * invGrid.y * FastFunctions.DoublePi; _fieldSum[index] = 0f; for (int k = 0; k < Waves.Length; k++) { Wave wave = Waves[k]; // Calculating new node value if (wave.Excluded) { continue; } float val; if (wave.Circular) { normX += wave.CircleShift.x; normY += wave.CircleShift.y; /* Non-optimized version. Use it if you want */ //val = FastFunctions.FastSqrt(normX * normX + normY * normY) * wave.Frequency + _time * wave.Velocity; val = normX * normX + normY * normY; FastFunctions.FloatIntUnion u; u.i = 0; u.f = val; float xhalf = 0.5f * val; u.i = 0x5f375a86 - (u.i >> 1); u.f = u.f * (1.5f - xhalf * u.f * u.f); val = u.f * val * wave.Frequency + _time * wave.Velocity; } else { val = (wave.Direction.x * normX + wave.Direction.y * normY) * wave.Frequency + _time * wave.Velocity; } /* Non-optimized version. Use it if you want */ //_fieldSum[index] += FastFunctions.FastPow((FastFunctions.FastSin(val) + 1f) * 0.5f, wave.Steepness) * wave.Amplitude * obstructionValue; /* Optimized version */ // FastFunctions.FastSin(val) float tmpVal = (val + Mathf.PI) * FastFunctions.InvDoublePi; int floor = tmpVal >= 0 ? (int)(tmpVal) : (int)((tmpVal) - 1); val = val - FastFunctions.DoublePi * floor; if (val < 0) { val = 1.27323954f * val + 0.405284735f * val * val; } else { val = 1.27323954f * val - 0.405284735f * val * val; } // FastFunctions.FastPowInt tmpVal = (val + 1f) * 0.5f; int steepness = wave.Steepness; while (steepness > 0) { tmpVal *= val; steepness--; } // Final calculations val = tmpVal * wave.Amplitude * obstructionValue; _fieldSum[index] += val; /* End of optimized version */ } if (!OnlyAmbient) { _fieldSum[index] += FieldSimNew[index]; } } } // Updating the field _field = _fieldSum; _isDirty = true; }
/// <summary> /// Buoyancy calculation step. /// </summary> private void FixedUpdate() { // Happens on assembly reload if (_recompileMarker == null) { RecalculateVoxels(); _water = null; _recompileMarker = new RecompiledMarker(); } // Failsafe if (_water == null || _density < 0.01f || !_isReady) { _rigidbody.drag = _dragNonFluid; _rigidbody.angularDrag = _angularDragNonFluid; return; } float invDoubleVoxelSize = 1f / (2f * _voxelSize); DynamicWater dynamicWater = _water as DynamicWater; bool solverCanInteract = dynamicWater != null && dynamicWater.Solver.CanInteract; for (int i = 0; i < _voxelsLength; i++) { Vector3 wp = _transform.TransformPoint(_buoyancyVoxels[i].Position); float waterLevel = _water.GetWaterLevel(wp.x, wp.y, wp.z); // No force is applied to the points outside the fluid if (waterLevel != float.NegativeInfinity && (wp.y - _voxelSize / 1f < waterLevel)) { Vector3 velocity = _rigidbody.GetPointVelocity(wp); // k == 1 when the voxel is fully submerged // k == 0 when the voxel is fully outside float k = (waterLevel - wp.y) * invDoubleVoxelSize + 0.5f; // Create the splash when the point has passed the water surface. if (_buoyancyVoxels[i].IsOnColliderEdge) { if (!_buoyancyVoxels[i].HadPassedWater && (k <1f && k> 0f)) { // Scaling and limiting the splash force if (solverCanInteract) { float force = FastFunctions.FastVector3Magnitude(velocity) * _splashForceFactorNormalized; if (force > _maxSplashForceNormalized) { force = _maxSplashForceNormalized; } if (force > 0.0075f) { _water.CreateSplash(wp, _voxelSize, force); } } _buoyancyVoxels[i].HadPassedWater = true; } else { _buoyancyVoxels[i].HadPassedWater = false; } } k = (k > 1f) ? 1f : (k < 0f) ? 0f : k; _subMergedVolume += k; // Calculating the actual force for this point depending oh how much // the point is submerged into the fluid Vector3 archimedesForce; archimedesForce.x = k * _voxelArchimedesForce.x; archimedesForce.y = k * _voxelArchimedesForce.y; archimedesForce.z = k * _voxelArchimedesForce.z; // Applying the local force _rigidbody.AddForceAtPosition(archimedesForce, wp, ForceMode.Impulse); } } // Normalizing the submerged volume // 0 - object is fully outside the water // 1 - object is fully submerged _subMergedVolume /= _voxelsLength; const float threshold = 0.01f; // Sending the message to other components if (_subMergedVolumePrev < threshold && _subMergedVolume >= threshold) { SendMessage("OnFluidVolumeEnter", _water, SendMessageOptions.DontRequireReceiver); } else if (_subMergedVolumePrev >= threshold && _subMergedVolume < threshold) { SendMessage("OnFluidVolumeExit", _water, SendMessageOptions.DontRequireReceiver); } _subMergedVolumePrev = _subMergedVolume; // Calculating the drag _rigidbody.drag = Mathf.Lerp(_rigidbody.drag, _subMergedVolume > 0.0001f ? _dragNonFluid + _dragInFluid : _dragNonFluid, 8f * Time.deltaTime); _rigidbody.angularDrag = Mathf.Lerp(_rigidbody.angularDrag, _subMergedVolume > 0.0001f ? _angularDragNonFluid + _angularDragInFluid : _angularDragNonFluid, 8f * Time.deltaTime); }