How to create the effect of a circular object entering and separating from a thick substance How to create the effect of a circular object entering and separating from a thick substance swift swift

How to create the effect of a circular object entering and separating from a thick substance


You are looking for a fluid simulator

This is indeed possible with modern hardware.Let's have a look at what we are going to build here.

enter image description here

Components

In order to achieve that we'll need to

  • create several molecules having a physics body and a blurred image
  • use a Shader to apply a common color to every pixel having alpha > 0

Molecule

Here's the Molecule class

import SpriteKitclass Molecule: SKSpriteNode {    init() {        let texture = SKTexture(imageNamed: "molecule")        super.init(texture: texture, color: .clear, size: texture.size())        let physicsBody = SKPhysicsBody(circleOfRadius: 8)        physicsBody.restitution = 0.2        physicsBody.affectedByGravity = true        physicsBody.friction = 0        physicsBody.linearDamping = 0.01        physicsBody.angularDamping = 0.01        physicsBody.density = 0.13        self.physicsBody = physicsBody    }    required init?(coder aDecoder: NSCoder) {        fatalError("init(coder:) has not been implemented")    }}

Shader

Next we need a Fragment Shader, let's create a file with name Water.fsh

void main() {    vec4 current_color = texture2D(u_texture, v_tex_coord);    if (current_color.a > 0) {        current_color.r = 0.0;        current_color.g = 0.57;        current_color.b = 0.95;        current_color.a = 1.0;    } else {        current_color.a = 0.0;    }    gl_FragColor = current_color;}

Scene

And finally we can define the scene

import SpriteKitclass GameScene: SKScene {    lazy var label: SKLabelNode = {        return childNode(withName: "label") as! SKLabelNode    }()    let effectNode = SKEffectNode()    override func didMove(to view: SKView) {        physicsBody = SKPhysicsBody(edgeLoopFrom: frame)        effectNode.shouldEnableEffects = true        effectNode.shader = SKShader(fileNamed: "Water")        addChild(effectNode)    }    var touchLocation: CGPoint?    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {        guard let touch = touches.first else { return }        let touchLocation = touch.location(in: self)        if label.contains(touchLocation) {            addRedCircle(location: touchLocation)        } else {            self.touchLocation = touchLocation        }    }    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {        touchLocation = nil    }    override func update(_ currentTime: TimeInterval) {        if let touchLocation = touchLocation {            let randomizeX = CGFloat(arc4random_uniform(20)) - 10            let randomizedLocation = CGPoint(x: touchLocation.x + randomizeX, y: touchLocation.y)            addMolecule(location: randomizedLocation)        }    }    private func addMolecule(location: CGPoint) {        let molecule = Molecule()        molecule.position = location        effectNode.addChild(molecule)    }    private func addRedCircle(location: CGPoint) {        let texture = SKTexture(imageNamed: "circle")        let sprite = SKSpriteNode(texture: texture)        let physicsBody = SKPhysicsBody(circleOfRadius: texture.size().width / 2)        physicsBody.restitution = 0.2        physicsBody.affectedByGravity = true        physicsBody.friction = 0.1        physicsBody.linearDamping = 0.1        physicsBody.angularDamping = 0.1        physicsBody.density = 1        sprite.physicsBody = physicsBody        sprite.position = location        addChild(sprite)    }}

The full project is available on my GitHub account https://github.com/lucaangeletti/SpriteKitAqua


From a high level of understanding there are two ways to do this.

  1. THE BAD WAY (but works better when fluid has textures): Create the sprite sheet in advance, then overlay an additional child of the SKSpriteNode object. The frame in the animation sprite will be a function of distance from the ball to the surface when distance between them is less than some amount. The desired distance range (range) will have to be mapped to the sprites frame number (frameIndex). f(range) = frameIndex. Linear interpolation will help out here. More on interpolation later.

  2. THE RIGHT WAY: Make the fluid a curve object, then animate the points on the curve with linear interpolation between the initial, intermediate and final states. This will require three curves each one with the same number of points. Let the initial fluid state be F1. Model F1 points as the static fluid. Let the fluid state be F2 when the ball is halfway submerged. Model F2 points to look like the ball submerged at its maximum width. Let the fluid state be F3 when the ball is 75% submerged. Notice that when the ball is fully submerged the fluid looks unchanged. This is why when the ball is 75% submerged it has the maximum surface tension grabbing the ball. As far as SpriteKit goes, you may use these objects:

    CGMutablePathRef path = CGPathCreateMutable();CGPathMoveToPoint(path, NULL, 0, 0);CGPathAddQuadCurveToPoint(path, NULL, 50, 100, 100, 0);CGPathAddLineToPoint(path, NULL, 50, -100);CGPathCloseSubpath(path);SKShapeNode *shape = [[SKShapeNode alloc]init];shape.path = path;

Then detect when the ball is on the outside of the fluid by using the vector cross product with 3D vectors, even if your project is in 2D.

 Ball Vector (Vb)    ^    |(V) O--->  Closest Fluid Surface Vector (Vs)V = Vb x Vs

Then look at the Z component of V called Vz.If (Vz < 0), the ball is outside of the fluid:Create a variable t:

t = distOfBall/radiusOfBall

Then for every ordered point in your fluid shapes do the following:

newFluidPointX = F1pointX*(t-1) + F2pointX*tnewFluidPointY = F1pointY*(t-1) + F2pointY*t

If Vz > 0), the ball is inside of the fluid:

t = -(((distOfBall/radiusOfBall) + 0.5)^2)  *4 + 1newFluidPointX = F2pointX*(t-1) + F3pointX*tnewFluidPointY = F2pointY*(t-1) + F3pointY*t

This works because any two shapes can be blended together using interpolation.The parameter "t" acts as a percentage to blend between two shapes.

You can actually create seamless blends between any two shapes, so long as the number of points are the same. This is how a man morphs into a wolf in Hollywood movies, or how a man can morph into a liquid puddle. The only principle in play for those effects is interpolation. Interpolation is a very powerful tool. It is defined as:

    L = A*(t-1) + B*t    where t is in between 0.0 and 1.0    and A and B is what you are morphing from and to.

For more on interpolation see:Wiki Article

For further study. If you are considering animating any dynamic shapes, I would consider understanding Bezier curves. Pomax has a wonderful article on the topic. Although many frameworks have curves in them, having a general understanding of how they work will allow you to manipulate them extensivly or roll your own features where the framework is lacking. Her is Pomax's article:

A Primer on Curves

Good luck on your progress:)