private void ApplyAnimationConstraints(Vector2 newSize) { float xFactor = 0f; float yFactor = 0f; bool hasBottomConstraint = HasConstraintDefined(WaterAnimationConstraint.Bottom); bool hasVerticalConstraint = hasBottomConstraint || HasConstraintDefined(WaterAnimationConstraint.Top); if (hasVerticalConstraint) { yFactor = hasBottomConstraint ? 1f : -1f; } bool hasLeftConstraint = HasConstraintDefined(WaterAnimationConstraint.Left); bool hasHorizontalConstraint = hasLeftConstraint || HasConstraintDefined(WaterAnimationConstraint.Right); if (hasHorizontalConstraint) { xFactor = hasLeftConstraint ? 1f : -1f; } // Calculating new water position = currentPosition ± deltaChange * 0.5f // we're working here in local space so the current water position is always equal to (0,0) // the newPosition = ± deltaChange * 0.5f // ± : the sign depends on the defined constraints float x = ((newSize.x - _lastWaterSize.x) * 0.5f) * xFactor; float y = ((newSize.y - _lastWaterSize.y) * 0.5f) * yFactor; _mainModule.Position = _mainModule.TransformPointLocalToWorld(new Vector3(x, y)); }
/// <summary> /// Generate a ripple at a particular position. /// </summary> /// <param name="position">Ripple position.</param> /// <param name="disturbanceFactor">Range: [0..1]: The disturbance is linearly interpolated between the minimum disturbance and the maximum disturbance by this factor.</param> /// <param name="pullWaterDown">Pull water down or up?</param> /// <param name="playSoundEffect">Play the sound effect.</param> /// <param name="playParticleEffect">Play the particle effect.</param> /// <param name="smoothRipple">Disturb neighbor vertices to create a smoother ripple (wave).</param> /// <param name="smoothingFactor">Range: [0..1]: The amount of disturbance to apply to neighbor vertices.</param> public void GenerateRipple(Vector2 position, float disturbanceFactor, bool pullWaterDown, bool playSoundEffect, bool playParticleEffect, bool smoothRipple, float smoothingFactor = 0.5f) { float xPosition = _mainModule.TransformPointWorldToLocal(position).x; float leftBoundary = _simulationModule.LeftBoundary; float rightBoundary = _simulationModule.RightBoundary; int surfaceVertexCount = _meshModule.SurfaceVerticesCount; int leftMostSurfaceVertexIndex = _simulationModule.IsUsingCustomBoundaries ? 1 : 0; int rightMostSurfaceVertexIndex = _simulationModule.IsUsingCustomBoundaries ? surfaceVertexCount - 2 : surfaceVertexCount - 1; if (xPosition < leftBoundary || xPosition > rightBoundary) { return; } float disturbance = (pullWaterDown ? -1f : 1f) * Mathf.Lerp(_minimumDisturbance, _maximumDisturbance, Mathf.Clamp01(disturbanceFactor)); float subdivisionsPerUnit = (surfaceVertexCount - (_simulationModule.IsUsingCustomBoundaries ? 3 : 1)) / (rightBoundary - leftBoundary); float delta = (xPosition - leftBoundary) * subdivisionsPerUnit; int nearestVertexIndex = leftMostSurfaceVertexIndex + Mathf.RoundToInt(delta); _simulationModule.DisturbSurfaceVertex(nearestVertexIndex, disturbance); if (smoothRipple) { smoothingFactor = Mathf.Clamp01(smoothingFactor); float smoothedDisturbance = disturbance * smoothingFactor; int previousNearestIndex = nearestVertexIndex - 1; if (previousNearestIndex >= leftMostSurfaceVertexIndex) { _simulationModule.DisturbSurfaceVertex(previousNearestIndex, smoothedDisturbance); } int nextNearestIndex = nearestVertexIndex + 1; if (nextNearestIndex <= rightMostSurfaceVertexIndex) { _simulationModule.DisturbSurfaceVertex(nextNearestIndex, smoothedDisturbance); } } Vector3 spawnPosition = _mainModule.TransformPointLocalToWorld(new Vector3(xPosition, _mainModule.Height * 0.5f)); if (playParticleEffect) { _particleEffect.PlayParticleEffect(spawnPosition); } if (playSoundEffect) { _soundEffect.PlaySoundEffect(spawnPosition, disturbanceFactor); } }
/// <summary> /// Generate a ripple at a particular position. /// </summary> /// <param name="position">Ripple position.</param> /// <param name="disturbanceFactor">Range: [0..1]: The disturbance is linearly interpolated between the minimum disturbance and the maximum disturbance by this factor.</param> /// <param name="pullWaterDown">Pull water down or up?</param> /// <param name="playSoundEffect">Play the sound effect.</param> /// <param name="playParticleEffect">Play the particle effect.</param> /// <param name="smoothRipple">Disturb neighbor vertices to create a smoother ripple (wave).</param> /// <param name="smoothingFactor">Range: [0..1]: The amount of disturbance to apply to neighbor vertices.</param> public void GenerateRipple(Vector2 position, float disturbanceFactor, bool pullWaterDown, bool playSoundEffect, bool playParticleEffect, bool smoothRipple, float smoothingFactor = 0.5f) { float xPosition = _mainModule.TransformPointWorldToLocal(position).x; float leftBoundary = _simulationModule.LeftBoundary; float rightBoundary = _simulationModule.RightBoundary; int surfaceVerticesCount = _meshModule.SurfaceVerticesCount; int startIndex = _simulationModule.IsUsingCustomBoundaries ? 1 : 0; int endIndex = _simulationModule.IsUsingCustomBoundaries ? surfaceVerticesCount - 2 : surfaceVerticesCount - 1; if (xPosition < leftBoundary || xPosition > rightBoundary) return; float disturbance = Mathf.Lerp(_minimumDisturbance, _maximumDisturbance, Mathf.Clamp01(disturbanceFactor)); float velocity = (pullWaterDown ? -1f : 1f) * _simulationModule.StiffnessSquareRoot * disturbance; float delta = (xPosition - leftBoundary) * _meshModule.SubdivisionsPerUnit; int nearestVertexIndex = startIndex + Mathf.RoundToInt(delta); var velocities = _simulationModule.Velocities; velocities[nearestVertexIndex] += velocity; if (smoothRipple) { smoothingFactor = Mathf.Clamp01(smoothingFactor); float smoothedVelocity = velocity * smoothingFactor; int previousNearestIndex = nearestVertexIndex - 1; if (previousNearestIndex >= startIndex) velocities[previousNearestIndex] += smoothedVelocity; int nextNearestIndex = nearestVertexIndex + 1; if (nextNearestIndex <= endIndex) velocities[nextNearestIndex] += smoothedVelocity; } _simulationModule.MarkVelocitiesArrayAsChanged(); Vector3 spawnPosition = _mainModule.TransformPointLocalToWorld(new Vector3(xPosition, _mainModule.Height * 0.5f)); if (playParticleEffect) _particleEffect.PlayParticleEffect(spawnPosition); if (playSoundEffect) _soundEffect.PlaySoundEffect(spawnPosition, disturbanceFactor); }
private void AffectRigidbodies() { var inWaterColliders = GetInWaterColliders(); if (inWaterColliders.Count > 0) { float waterRestPosition = _mainModule.Height * 0.5f; float waterBuoyancyLevel = _mainModule.Height * (0.5f - _waterObject.AttachedComponentsModule.BuoyancyEffectorSurfaceLevel); float offset = waterRestPosition - waterBuoyancyLevel; float leftBoundary = LeftBoundary; int surfaceVertexCount = _meshModule.SurfaceVerticesCount; int leftMostSurfaceVertexIndex = _isUsingCustomBoundaries ? 1 : 0; int rightMostSurfaceVertexIndex = _isUsingCustomBoundaries ? surfaceVertexCount - 2 : surfaceVertexCount - 1; float subdivisionsPerUnit = _meshModule.SurfaceVerticesCount / _mainModule.Width; float cellWidth = 1f / subdivisionsPerUnit; float density = _attachedComponentsModule.BuoyancyEffector.density; Vector2 gravity = Physics2D.gravity; Vector3[] vertices = _meshModule.Vertices; foreach (var collider in inWaterColliders) { if (collider == null) { _destroyedCollidersToRemoveFromInWaterCollidersSet.Enqueue(collider); continue; } Bounds colliderBounds = collider.bounds; Vector2 colliderBoundsCenterWaterSpace = _mainModule.TransformPointWorldToLocal(colliderBounds.center); Vector2 colliderBoundsExtents = colliderBounds.extents; float yMaxColliderBounds = colliderBoundsCenterWaterSpace.y + colliderBoundsExtents.y; float yMinColliderBounds = colliderBoundsCenterWaterSpace.y - colliderBoundsExtents.y; if (yMaxColliderBounds > waterBuoyancyLevel) { int nearestStartVertexIndex = Mathf.Clamp(Mathf.RoundToInt((colliderBoundsCenterWaterSpace.x - colliderBoundsExtents.x - leftBoundary) * subdivisionsPerUnit), leftMostSurfaceVertexIndex, rightMostSurfaceVertexIndex); int nearestEndVertexIndex = Mathf.Clamp(Mathf.RoundToInt((colliderBoundsCenterWaterSpace.x + colliderBoundsExtents.x - leftBoundary) * subdivisionsPerUnit), leftMostSurfaceVertexIndex, rightMostSurfaceVertexIndex); Vector2 centroid = Vector2.zero; float totalArea = 0f; for (int i = nearestStartVertexIndex; i < nearestEndVertexIndex; i++) { Vector2 currentSurfaceVertexPosition = vertices[i]; currentSurfaceVertexPosition.y -= offset; if (currentSurfaceVertexPosition.y > yMaxColliderBounds) { currentSurfaceVertexPosition.y = yMaxColliderBounds; } else if (currentSurfaceVertexPosition.y < yMinColliderBounds) { currentSurfaceVertexPosition.y = yMinColliderBounds; } float cellHeight = currentSurfaceVertexPosition.y - waterBuoyancyLevel; Vector2 cellCenter = currentSurfaceVertexPosition + 0.5f * new Vector2(cellWidth, -cellHeight); float cellArea = cellHeight * cellWidth; totalArea += cellArea; if (Mathf.Abs(totalArea) > 0f) { centroid += (cellArea / totalArea) * (cellCenter - centroid); } } var attachedRigidbody = collider.attachedRigidbody; Vector2 force = (_wavesStrengthOnRigidbodies * totalArea * density * attachedRigidbody.gravityScale) * -gravity; attachedRigidbody.AddForceAtPosition(force, _mainModule.TransformPointLocalToWorld(centroid)); } } while (_destroyedCollidersToRemoveFromInWaterCollidersSet.Count > 0) { inWaterColliders.Remove(_destroyedCollidersToRemoveFromInWaterCollidersSet.Dequeue()); } } }
internal void ResolveCollision(Collider2D objectCollider, bool isObjectEnteringWater) { if ((isObjectEnteringWater && !_isOnWaterEnterRipplesActive) || (!isObjectEnteringWater && !_isOnWaterExitRipplesActive)) { return; } if (_collisionMask != (_collisionMask | (1 << objectCollider.gameObject.layer))) { return; } Vector3[] vertices = _meshModule.Vertices; float[] velocities = _simulationModule.Velocities; float stiffnessSquareRoot = _simulationModule.StiffnessSquareRoot; Vector3 raycastDirection = _mainModule.UpDirection; int surfaceVerticesCount = _meshModule.SurfaceVerticesCount; int subdivisionsPerUnit = _meshModule.SubdivisionsPerUnit; Bounds objectColliderBounds = objectCollider.bounds; float minColliderBoundsX = _mainModule.TransformPointWorldToLocal(objectColliderBounds.min).x; float maxColliderBoundsX = _mainModule.TransformPointWorldToLocal(objectColliderBounds.max).x; int firstSurfaceVertexIndex = _simulationModule.IsUsingCustomBoundaries ? 1 : 0; int lastSurfaceVertexIndex = _simulationModule.IsUsingCustomBoundaries ? surfaceVerticesCount - 2 : surfaceVerticesCount - 1; float firstSurfaceVertexPosition = vertices[firstSurfaceVertexIndex].x; int startIndex = Mathf.Clamp(Mathf.RoundToInt((minColliderBoundsX - firstSurfaceVertexPosition) * subdivisionsPerUnit), firstSurfaceVertexIndex, lastSurfaceVertexIndex); int endIndex = Mathf.Clamp(Mathf.RoundToInt((maxColliderBoundsX - firstSurfaceVertexPosition) * subdivisionsPerUnit), firstSurfaceVertexIndex, lastSurfaceVertexIndex); int hitPointsCount = 0; float hitPointsVelocitiesSum = 0f; Vector2 hitPointsPositionsSum = new Vector2(0f, 0f); for (int surfaceVertexIndex = startIndex; surfaceVertexIndex <= endIndex; surfaceVertexIndex++) { Vector2 surfaceVertexPosition = _mainModule.TransformPointLocalToWorld(vertices[surfaceVertexIndex]); RaycastHit2D hit = Physics2D.Raycast(surfaceVertexPosition, raycastDirection, _collisionRaycastMaximumDistance, _collisionMask, _collisionMinimumDepth, _collisionMaximumDepth); if (hit.collider == objectCollider && hit.rigidbody != null) { float velocity = hit.rigidbody.GetPointVelocity(surfaceVertexPosition).y *_velocityMultiplier; velocity = Mathf.Clamp(Mathf.Abs(velocity), _minimumDisturbance, _maximumDisturbance); velocities[surfaceVertexIndex] += velocity * stiffnessSquareRoot * (isObjectEnteringWater ? -1f : 1f); hitPointsVelocitiesSum += velocity; hitPointsPositionsSum += hit.point; hitPointsCount++; } } if (hitPointsCount > 0) { _simulationModule.MarkVelocitiesArrayAsChanged(); Vector2 hitPointsPositionsMean = hitPointsPositionsSum / hitPointsCount; Vector3 spawnPosition = new Vector3(hitPointsPositionsMean.x, hitPointsPositionsMean.y, _mainModule.Position.z); float hitPointsVelocitiesMean = hitPointsVelocitiesSum / hitPointsCount; float disturbanceFactor = Mathf.InverseLerp(_minimumDisturbance, _maximumDisturbance, hitPointsVelocitiesMean); if (isObjectEnteringWater) { OnWaterEnterRipplesParticleEffect.PlayParticleEffect(spawnPosition); OnWaterEnterRipplesSoundEffect.PlaySoundEffect(spawnPosition, disturbanceFactor); if (_onWaterEnter != null) { _onWaterEnter.Invoke(); } } else { OnWaterExitRipplesParticleEffect.PlayParticleEffect(spawnPosition); OnWaterExitRipplesSoundEffect.PlaySoundEffect(spawnPosition, disturbanceFactor); if (_onWaterExit != null) { _onWaterExit.Invoke(); } } } }
internal void PhysicsUpdate(float deltaTime) { if (!_isActive || (!_updateWhenOffscreen && !_mainModule.IsVisible)) { return; } _elapsedTime += deltaTime; if (_elapsedTime >= _currentInterval) { if (_randomizeRipplesSourcePositions) { RandomizeIndices(); } int surfaceVerticesCount = _meshModule.SurfaceVerticesCount; int startIndex = _simulationModule.IsUsingCustomBoundaries ? 1 : 0; int endIndex = _simulationModule.IsUsingCustomBoundaries ? surfaceVerticesCount - 2 : surfaceVerticesCount - 1; Vector3[] vertices = _meshModule.Vertices; float[] velocities = _simulationModule.Velocities; bool playSoundEffect = _soundEffect.IsActive; bool playParticleEffect = _particleEffect.IsActive; for (int i = 0, imax = _ripplesSourcesIndices.Count; i < imax; i++) { int index = _ripplesSourcesIndices[i]; float disturbance = _randomizeDisturbance ? Random.Range(_minimumDisturbance, _maximumDisturbance) : _disturbance; disturbance *= _simulationModule.StiffnessSquareRoot; velocities[index] -= disturbance; if (_smoothRipples) { float smoothedDisturbance = disturbance * _smoothingFactor; int previousIndex, nextIndex; previousIndex = index - 1; nextIndex = index + 1; if (previousIndex >= startIndex) { velocities[previousIndex] -= smoothedDisturbance; } if (nextIndex <= endIndex) { velocities[nextIndex] -= smoothedDisturbance; } } #if UNITY_EDITOR if (!Application.isPlaying) { continue; } #endif if (_soundEffect.IsActive || _particleEffect.IsActive) { Vector3 spawnPosition = _mainModule.TransformPointLocalToWorld(vertices[index]); if (playParticleEffect) { _particleEffect.PlayParticleEffect(spawnPosition); } if (playSoundEffect) { float disturbanceFactor = Mathf.InverseLerp(_minimumDisturbance, _maximumDisturbance, disturbance); _soundEffect.PlaySoundEffect(spawnPosition, disturbanceFactor); } } } _currentInterval = _randomizeTimeInterval ? Random.Range(_minimumTimeInterval, _maximumTimeInterval) : _timeInterval; _elapsedTime = 0f; _simulationModule.MarkVelocitiesArrayAsChanged(); } }