41 votes

Comment créer l'effet d'un objet circulaire qui entre et se sépare d'une substance épaisse ?

Sur la base de l'image ci-dessous (j'ai utilisé des couleurs différentes pour le cercle et la surface plane pour qu'on puisse les voir, mais au final les couleurs seront les mêmes), en utilisant Swift et Spritekit, j'essaie de créer l'effet d'un objet circulaire entrant dans une substance épaisse (pas nécessairement collante) et se séparant de la substance épaisse. En fait, lorsque l'objet circulaire se sépare, il s'éloigne de la surface plane en formant un cercle. Je voulais utiliser des images d'animation, mais comme les objets sont des SKSpriteNodes avec des corps physiques, il sera assez difficile de synchroniser la collision des objets avec l'animation. Une autre approche consisterait à utiliser CAAnimation, mais je ne sais pas comment la combiner avec SKSpriteNodes avec des corps physiques. Comment puis-je créer cet effet de séparation en utilisant l'une des approches mentionnées ci-dessus ou une autre ?

enter image description here

UPDATE

L'image ci-dessous montre le changement de la surface de la substance épaisse lorsque l'objet circulaire pénètre dans la substance épaisse jusqu'à ce qu'il soit submergé.

enter image description here

1 votes

Je ne vois pas bien quel aspect de "pénétrer dans une substance épaisse" vous essayez de capturer. La vitesse de l'objet circulaire ? La couleur lorsqu'il rencontre la "substance épaisse" ? La tension superficielle de la substance plus épaisse ? Vous pouvez peut-être inclure un dessin de l'effet que vous recherchez...

2 votes

Je fais référence à la tension de surface de la substance la plus épaisse.

0 votes

Est-ce que c'est quelque chose que vous recherchez ? pasteboard.co/1zoNKPiJ.png J'y ai deux cercles que vous faites glisser et que vous reliez en douceur. Je crée une forme personnalisée entre deux cercles en utilisant un chemin de bézier et une certaine géométrie.

2voto

appzYourLife Points 10219

Vous recherchez un simulateur de fluide

C'est en effet possible avec le matériel moderne. Voyons un peu ce que nous allons construire ici.

enter image description here

Composants

Pour cela, nous devons

  • créer plusieurs molécules ayant un corps physique et une image floue
  • utiliser un Shader pour appliquer une couleur commune à chaque pixel ayant un alpha > 0

Molécule

Voici la classe Molecule

import SpriteKit

class 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

Ensuite, nous avons besoin d'un Fragment Shader, créons un fichier dont le nom est 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;
}

Scène

Et enfin, nous pouvons définir la scène

import SpriteKit

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

}

Le projet complet est disponible sur mon compte GitHub https://github.com/lucaangeletti/SpriteKitAqua

1voto

Eric Bischoff Points 13

D'un point de vue général, il existe deux façons de procéder.

  1. LA MAUVAISE MANIÈRE (mais fonctionne mieux lorsque le fluide a des textures) : Créez la feuille de sprite à l'avance, puis superposez un enfant supplémentaire de l'objet SKSpriteNode. L'image dans le sprite d'animation sera une fonction de la distance entre la balle et la surface lorsque la distance entre elles est inférieure à une certaine quantité. La plage de distance souhaitée (range) devra être mise en correspondance avec le numéro d'image du sprite (frameIndex). f(range) = frameIndex. L'interpolation linéaire sera utile ici. Nous reviendrons plus tard sur l'interpolation.

  2. LA BONNE FAÇON : faites du fluide un objet courbe, puis animez les points de la courbe par interpolation linéaire entre les états initial, intermédiaire et final. Cela nécessitera trois courbes ayant chacune le même nombre de points. L'état initial du fluide est F1. Modélisez les points F1 comme le fluide statique. Soit l'état du fluide F2 lorsque la balle est à moitié submergée. Modélisez les points F2 pour qu'ils ressemblent à la boule submergée à sa largeur maximale. Soit l'état du fluide F3 lorsque la balle est immergée à 75%. Remarquez que lorsque la balle est complètement submergée, le fluide semble inchangé. C'est pourquoi, lorsque la balle est immergée à 75 %, la tension superficielle qui la saisit est maximale. En ce qui concerne SpriteKit, vous pouvez utiliser ces objets :

    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;

Détectez ensuite quand la balle se trouve à l'extérieur du fluide en utilisant le produit vectoriel croisé avec des vecteurs 3D, même si votre projet est en 2D.

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

V = Vb x Vs

Regardez ensuite la composante Z de V appelée Vz. Si (Vz < 0), la balle est à l'extérieur du fluide : Créez une variable t :

t = distOfBall/radiusOfBall

Ensuite, pour chaque point ordonné dans vos formes fluides, faites ce qui suit :

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

Si Vz > 0), la boule est à l'intérieur du fluide :

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

Cela fonctionne parce que deux formes quelconques peuvent être mélangées en utilisant l'interpolation. Le paramètre "t" agit comme un pourcentage de fusion entre deux formes.

Vous pouvez en fait créer des mélanges sans couture entre deux formes quelconques, pour autant que le nombre de points soit le même. C'est ainsi qu'un homme se transforme en loup dans les films hollywoodiens, ou qu'un homme peut se transformer en une flaque d'eau. Le seul principe en jeu pour ces effets est l'interpolation. L'interpolation est un outil très puissant. Elle est définie comme suit :

    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.

Pour en savoir plus sur l'interpolation, voir : Article Wiki

Pour une étude plus approfondie. Si vous envisagez d'animer des formes dynamiques, je vous conseille de comprendre les courbes de Bézier. Pomax a un article merveilleux sur le sujet. Bien que de nombreux frameworks intègrent des courbes, une compréhension générale de leur fonctionnement vous permettra de les manipuler de manière intensive ou de créer vos propres fonctionnalités là où le framework fait défaut. Voici l'article de Pomax :

Une introduction aux courbes

Bonne chance pour vos progrès :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X