Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I "Fold" a UIView in 3D along a line?

If you have a UIView

enter image description here

Imagine the view being full of text, animations, a cat photo, a gradient, or indeed any of the stuff a UIView can be full of.

It's easy to tilt it back to the left in 3D

enter image description here

or back to the right ..

enter image description here

(BTW I highly recommend the perfect OHCubeView ohcubeview when you have to do that sort of thing.)

But I want to crease a view along a seam, and have one side going backwards to the left and the other side going backwards to the right ..

enter image description here

!

My first solution was simply: whatever the view is (the user is typing in text, a photo of a cat, whatever), just perfectly duplicate it, and then solve the problem in the obvious way.

That's inelegant, but does work.

But...

the thing I want to fold like this, is ... an SKView with an emitter in it.

Particle emitters are of course random, and while two of the same one will look similar, it's not a real solution.

Essentially, if someone knows how to "copy" a UIView to another, well, view, or something ...

enter image description here

the problem would be solved.

For a problem like this in a game engine, say, you just throw a "virtual camera" (whatever the concept is in your favorite game engine) on the thing in question, and put the other half, or whatever, anywhere you want, but, I don't know the analogy of that in iOS!

Does anyone have an angle on this:

enter image description here

like image 292
Fattie Avatar asked Nov 07 '22 10:11

Fattie


1 Answers

There's 2 approaches I could think of.

1) Bend Approach

You could bend your scene using an SKWarpGeometry. Semantically, you'd envision a 2 x 2 quadrant grid on your game scene, described by a 3 x 3 point data structure. You'd move the left and right edges in a little, and make them a little smaller. And you'd make the centre vertical edge a little longer.

Apple's documentation also mentions that essentially any type of node (including an SKEmitterNode), can be warped by putting it into an SKEffectNode, and applying the warp to that. See: https://developer.apple.com/documentation/spritekit/skwarpgeometrygrid/animate_the_warping_of_a_sprite?language=objc.

All we have to do is make an effect node be the root node of everything, and apply the warp to it. Now, this works, but as soon as there's an emitter in it, the warp starts spazzing in seizure-like convulsions. I regard this as a bug. I'll include the code anyway incase its fixed later on, because it's supposed to work according to the documentation.

let sourcePoints = [
    // bottom row: left, center, right
    vector_float2(0.0, 0.0),
    vector_float2(0.5, 0.0),
    vector_float2(1.0, 0.0),

    // middle row: left, center, right
    vector_float2(0.0, 0.5),
    vector_float2(0.5, 0.5),
    vector_float2(1.0, 0.5),

    // top row: left, center, right
    vector_float2(0.0, 1.0),
    vector_float2(0.5, 1.0),
    vector_float2(1.0, 1.0)
]

var destinationPoints = sourcePoints

// Bring lefts in, and make the edge shorter
destinationPoints[0] = vector_float2(0.1, 0.1)
destinationPoints[3] = vector_float2(0.1, 0.4)
destinationPoints[6] = vector_float2(0.1, 0.9)

// Bring rights in, and make the edge shorter
destinationPoints[2] = vector_float2(0.9, 0.1)
destinationPoints[5] = vector_float2(0.9, 0.4)
destinationPoints[8] = vector_float2(0.9, 0.9)

// Make the center edge longer
destinationPoints[1] = vector_float2(0.5, -0.2)
destinationPoints[4] = vector_float2(0.5, 0.7)
destinationPoints[7] = vector_float2(0.5, 1.2)

// Need to set the no warp geometry first
effectNode.warpGeometry = SKWarpGeometryGrid(columns: 2, rows: 2)

let newGeometry = SKWarpGeometryGrid(columns: 2, rows: 2,
                                         sourcePositions: sourcePoints,
                                         destinationPositions: destinationPoints)

effectNode.run(SKAction.warp(to: newGeometry, duration: 1.0)!)

Sources used:

https://www.hackingwithswift.com/example-code/games/how-to-warp-a-sprite-using-skwarpgeometrygrid

http://sound-of-silence.com/?page=blog&sort=recent&count=0

2) Live Copy Approach

This is simpler, there's far less code, and it's a lot more versatile; you can do things like in this Rayman Legends level https://youtu.be/7m5YQrucis8?t=1226. Also, it works.

Basically, we have our master SKView showing the master scene. We'll add a copy SKView showing a copy scene. The copy scene will have only one sprite node in it. In the master scene's update loop, we'll grab a flattened texture of the scene and assign it to the sprite node in the copy scene. An object that is the scene's delegate can tap into this update loop, so we'll make the view controller conform to SKSceneDelegate.

class Playground_VC: UIViewController, SKSceneDelegate {

    var sprite: SKSpriteNode?

    override func loadView() {
        self.view = SKView()
    }

    var skView: SKView {
        return self.view as! SKView
    }

    override func viewWillAppear(_ animated: Bool) {
        let scene = SKScene(size: self.skView.bounds.size)
        scene.backgroundColor = UIColor.clear
        scene.delegate = self
        self.skView.presentScene(scene)

        let e = newEmitter()
        scene.addChild(e)
        e.position = self.skView.midPoint

        self.createCopy()

    }

    func update(_ currentTime: TimeInterval, for scene: SKScene) {
        // Grab the root texture and assign to the sprite that's in the copy scene
        let t = self.skView.texture(from: self.skView.scene!)
        self.sprite?.texture = t
    }

    func createCopy() {

        // Make a new SKView that's 4 times smaller

        var f = self.skView.bounds
        f.size.height *= 0.4
        f.size.width *= 0.4

        let copy = SKView(frame: f)
        copy.presentScene(SKScene(size: f.size))
        self.view.addSubview(copy)

        let sprite = SKSpriteNode(texture: nil, size: f.size)
        copy.scene?.addChild(sprite)
        self.sprite = sprite
        sprite.position = CGPoint(x: f.size.width / 2, y: f.size.height / 2)

    }

    func newEmitter() -> SKEmitterNode {
        return SKEmitterNode(fileNamed: "Particle.sks")!
    }
}

Any manipulations you perform on the master SKView, like tilting it in 3D space, shouldn't affect the appearance of the copy, because the game scene is unaffected by those manipulations.

enter image description here

like image 98
Peter Parker Avatar answered Nov 14 '22 20:11

Peter Parker