iOS UIView 3D border - ios

I have been working on coin like UIView, which has 3d rotation.
I was able to implement rotate effect with CATransform3dRotate, based on the gesture. Now I need to implement shadow like effect, when image rotates.
Please refer to the image attached herewith.
Any suggestions? recommendations? or sample code would be great
Sample Requirement

You might want to look at SceneKit. I've put together a quick demo, which you can download here, of a 3D coin object which rotates horizontally left or right by a swipe gesture, with a realistic light source and shadowing. The relevant code follows:
import UIKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet weak var sceneView: SCNView!
let rotate90AboutZ = SCNAction.rotateByX(0.0, y: 0.0, z: CGFloat(M_PI_2), duration: 0.0)
var currentAngle: Float = 0.0
var coinNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.userInteractionEnabled = true
sceneView.backgroundColor = UIColor.blackColor()
sceneView.autoenablesDefaultLighting = true
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(true)
coinSceneSetup()
}
func coinSceneSetup() {
let coinScene = SCNScene()
//coin is rendered as a cylinder with a very small height
let coinGeometry = SCNCylinder(radius: 50, height: 2)
coinNode = SCNNode(geometry: coinGeometry)
coinNode.position = SCNVector3Make(0.0, 25.0, 25.0)
coinScene.rootNode.addChildNode(coinNode)
//rotate coin 90 degrees about the z axis so that it stands upright
coinNode.runAction(rotate90AboutZ)
let shinyCoinMaterial = SCNMaterial()
shinyCoinMaterial.diffuse.contents = UIColor.lightGrayColor()
shinyCoinMaterial.specular.contents = UIColor.whiteColor()
shinyCoinMaterial.shininess = 1.0
coinGeometry.firstMaterial = shinyCoinMaterial
sceneView.scene = coinScene
let panRecognizer = UIPanGestureRecognizer(target: self, action: "panGesture:")
sceneView.addGestureRecognizer(panRecognizer)
}
//allows for coin to spin with a right or left finger swipe, while still keeping it rotated 90 degrees about z axis
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
var newAngle = (Float)(translation.x)*(Float)(M_PI)/180.0
newAngle += currentAngle
coinNode.runAction(SCNAction.rotateToX(CGFloat(newAngle), y: 0.0, z: CGFloat(M_PI_2), duration: 0.0))
if(sender.state == UIGestureRecognizerState.Ended) {
currentAngle = newAngle
}
}
}
Several excellent introductory tutorials are available, for example here, here, and here. weheartswift.com put out a three part tutorial in Swift that I personally find especially useful. Here are parts 1, 2 and 3.
Jeff Lamarche's introduction to SceneKit on OS X, along with his accompanying source code is a valuable resource as well.

Related

Swift SKNode position help - sprite position always 0,0 as it moves

Problem: I’m working in swift playground on an iPad Pro.
I’d like to get the current position coordinates of an SKSpriteNode with attached physicsBody as they move around the screen. No matter what it try, the sprite position returns 0,0, despite seeing it move across the screen.
I’ve attempted various implementations of convert(_:to:) and convert(_:from:) but the sprite position always ends up 0,0. My understanding is that this isn’t really necessary in the specific case here since the sprite is the direct child of the scene so the sprite’s position property should already be what it is in the scene coordinate system.
I’ve spent a lot of googling time attempting to figure this out without much success which makes me think I’m asking the wrong question. If so, please help me figure out what the right question to ask is!
The touch position print returns the correct position relative to the initial screen size. That’s what I’d like to get for the sprite.
Disclaimer - i don’t do this professionally so please forgive what I’m sure is ugly coding!
import SwiftUI
import SpriteKit
import PlaygroundSupport
var screenW = CGFloat(2732)
var screenH = CGFloat(2048)
struct ContentView: View {
var scene: SKScene {
let scene = GameScene()
scene.size = CGSize(width: 2732, height: 2048)
scene.scaleMode = .aspectFit
scene.backgroundColor = #colorLiteral(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
return scene
}
class GameScene: SKScene {
var ship1 = SKSpriteNode()
public override func didMove(to view: SKView) {
// gravity edge around screen for convenience
physicsBody = SKPhysicsBody(edgeLoopFrom: frame)
//turn off default 1g gravity
physicsWorld.gravity = .zero
//Add a ship sprite
let ship1 = SKSpriteNode(imageNamed: "ship.PNG")
ship1.setScale(0.2)
ship1.position = CGPoint(x: screenW * 0.2, y: screenH * 0.2)
ship1.physicsBody = SKPhysicsBody(rectangleOf: ship1.size)
ship1.physicsBody?.velocity = CGVector(dx: 0, dy: 100)
addChild(ship1)
}
//touch handling begin
public override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
//
let location = touch.location(in: self)
print(location)
print(ship1.position)
}
}
var body: some View {
SpriteView(scene: scene)
.ignoresSafeArea()
}
}
//star the view
let currentView = ContentView()
//Playground view setup
PlaygroundPage.current.setLiveView(currentView)
PlaygroundPage.current.wantsFullScreenLiveView = true
PlaygroundPage.current.needsIndefiniteExecution = true

How to move a character back and forth across horizontally in swift

Currently trying to get a monster character to move across the screen horizontal back and forth. I'm extremely new to Swift as I just started learning last week and also haven't exactly master OOP. So how do I properly call the enemyStaysWithinPlayableArea func while it calls the addEnemy func. Heres what I have so far:
import SpriteKit
class GameScene: SKScene {
var monster = SKSpriteNode()
var monsterMove = SKAction()
var background = SKSpriteNode()
var gameArea: CGRect
// MARK: - Set area where user can play
override init(size: CGSize) {
// iphones screens have an aspect ratio of 16:9
let maxAspectRatio: CGFloat = 16.0/9.0
let playableWidth = size.height/maxAspectRatio
let margin = (size.width - playableWidth)/2
gameArea = CGRect(x: margin, y: 0, width: playableWidth, height: size.height)
super.init(size: size)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func didMove(to view: SKView) {
// I want to run addEnemy() while it checks for enemyStayWithinPlayableArea()
// run(SKAction.repeatForever(...)) ???
enemyStayWithinPlayableArea()
// MARK: - Setting the background
background = SKSpriteNode(imageNamed: "background")
background.size = self.size
// Set background at center of screen
background.position = CGPoint(x:self.size.width/2 ,y:self.size.height/2)
// Layering so everything in the game will be in front of the background
background.zPosition = 0
self.addChild(background)
}
func addEnemy() {
// MARK: - Set up enemy and its movements
monster = SKSpriteNode(imageNamed: "monster")
monster.zPosition = 5
// this is where the enemy starts
monster.position = CGPoint(x: 0, y: 300)
self.addChild(monster)
// I want it to move to the end of the screen within 1 sec
monsterMove = SKAction.moveTo(x: gameArea.maxX, duration: 1.0)
monster.run(monsterMove)
}
func enemyStayWithinPlayableArea(){
addEnemy()
// when the monster reaches the right most corner of the screen
// turn it back around and make it go the other way
if monster.position.x == gameArea.maxX{
monsterMove = SKAction.moveTo(x: gameArea.minX, duration: 1.0)
monster.run(monsterMove)
}
// when the monster reaches the left most corner of the screen
// same idea as above
if monster.position.x == gameArea.minX{
monsterMove = SKAction.moveTo(x: gameArea.maxX, duration: 1.0)
monster.run(monsterMove)
}
}
}
So far the enemy moves across the screen to the right edge of the screen, so that part works perfectly. I just need help making sure it continues in a loop (perhaps using the SKAction.repeatForever method but not sure how).
To understand movement of image in spritekit check this example.
let moveLeft = SKAction.moveTo(CGPoint(x: xpos, y: ypos), duration: duration)
let moveRight = SKAction.moveTo(CGPoint(x: xpos, y: ypos), duration: duration)
sprite.runAction(SKAction.repeatActionForever(SKAction.sequence([moveLeft, moveRight])))
for horizontal movement y position never been changed it should be same as in move left and move right.to stop repeatforever action use this.
sprite.removeAllActions()
Make two functions moveToMax and moveToMin with respective SKActions.And run action with completion handler.
func moveToMin(){
monsterMove = SKAction.moveTo(x: gameArea.minX, duration: 1.0)
monster.run(monsterMove, completion: {
moveToMax()
})
}

Animation of a circle in iOS

I'm learning how to use SpriteKit and would like to have a circle that:
Grows and shrinks smoothly
Pulses with a colour
Is displayed with crisp edges
So far, I've come up with the following code:
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
addCircle()
}
func addCircle(){
let circle = SKShapeNode(circleOfRadius: 50 ) // Size of Circle
circle.position = CGPointMake(frame.midX, frame.midY) //Middle of Screen
circle.glowWidth = 0.0 // No border
circle.fillColor = SKColor.yellowColor() // Start as yellow
let actualDuration = 1 // Animate for 1s
// Basic actions for grow, shrink, colour up, colour down
let actionGrow = SKAction.scaleTo(CGFloat(2), duration: NSTimeInterval(actualDuration))
actionGrow.timingMode = SKActionTimingMode.EaseInEaseOut
let actionShrink = SKAction.scaleTo(CGFloat(0.5), duration: NSTimeInterval(actualDuration))
actionShrink.timingMode = SKActionTimingMode.EaseInEaseOut
let actionColorUp = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 1.0, duration: NSTimeInterval(actualDuration))
let actionColorDown = SKAction.colorizeWithColor(UIColor.redColor(), colorBlendFactor: 0.0, duration: NSTimeInterval(actualDuration))
// Combine the actions
let actionGrowWithColor = SKAction.group([actionGrow,actionColorUp])
let actionShrinkWithColor = SKAction.group([actionShrink,actionColorDown])
// Run and repeat
circle.runAction(SKAction.repeatActionForever(SKAction.sequence([actionGrowWithColor,actionShrinkWithColor])))
// Add the circle
self.addChild(circle)
}
}
The first of my three criteria are met, but the other two are not.
As the SKShapeNode is not a vector, as it grows the edges are not crisp. Is there a better way to draw a circle, or should I just start with a circle that is sufficiently large?
Is there a reason why the colorizeWithColor section doesn't appear to have any effect?
Many thanks in advance!

SceneKit cube rotation with multiple UIPanGestureRecognizers

Thanks for taking a look at this. I'm sure it is something very basic. I am a beginner.
I am trying to rotate a cube in in a SceneKit view with pan gestures. I have so far been successful in load this sample app onto my iPad and panning my finger on the y axis to rotate the cube on its x axis, or panning along the screens x-axis to rotate the cube along its y-axis.
Currently, I've noticed that whichever gesture recognizer is added last to the sceneView is the one that works. My question is how can I have the cube respond to either a x pan gesture then a y pan gesture or vice versa.
Here is the code I have written so far:
import UIKit
import SceneKit
class ViewController: UIViewController {
//geometry
var geometryNode: SCNNode = SCNNode()
//gestures
var currentYAngle: Float = 0.0
var currentXAngle: Float = 0.0
override func viewDidLoad() {
super.viewDidLoad()
sceneSetup()
}
func sceneSetup () {
//setup scene
let scene = SCNScene()
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
//add camera
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 5.0)
//add light
let light = SCNLight()
light.type = SCNLightTypeOmni
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
//add cube
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let boxNode = SCNNode(geometry: cubeGeometry)
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(boxNode)
geometryNode = boxNode
//add recognizers
let panXRecognizer = UIPanGestureRecognizer(target: self, action: "rotateXGesture:")
let panYRecognizer = UIPanGestureRecognizer(target: self, action: "rotateYGesture:")
sceneView.addGestureRecognizer(panXRecognizer)
sceneView.addGestureRecognizer(panYRecognizer)
sceneView.scene = scene
}
func rotateXGesture (sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view)
var newXAngle = (Float)(translation.y)*(Float)(M_PI)/180.0
newXAngle += currentXAngle
geometryNode.transform = SCNMatrix4MakeRotation(newXAngle, 1, 0, 0)
if(sender.state == UIGestureRecognizerState.Ended) {
currentXAngle = newXAngle
}
}
func rotateYGesture (sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view)
var newYAngle = (Float)(translation.x)*(Float)(M_PI)/180.0
newYAngle += currentYAngle
geometryNode.transform = SCNMatrix4MakeRotation(newYAngle, 0, 1, 0)
if(sender.state == UIGestureRecognizerState.Ended) {
currentYAngle = newYAngle
}
}
}
Combine your current two gestures into one. Here's the relevant portion of the code I'm using:
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
var newAngleX = (Float)(translation.y)*(Float)(M_PI)/180.0
newAngleX += currentAngleX
var newAngleY = (Float)(translation.x)*(Float)(M_PI)/180.0
newAngleY += currentAngleY
baseNode.eulerAngles.x = newAngleX
baseNode.eulerAngles.y = newAngleY
if(sender.state == UIGestureRecognizerState.Ended) {
currentAngleX = newAngleX
currentAngleY = newAngleY
}
}
Here's a gesture for zooming as well:
func pinchGesture(sender: UIPinchGestureRecognizer) {
let zoom = sender.scale
var z = cameraNode.position.z * Float(1.0 / zoom)
z = fmaxf(zoomLimits.min, z)
z = fminf(zoomLimits.max, z)
cameraNode.position.z = z
}
Edit: I found a better way to rotate the model. In the panGesture code at the top, the x-axis pivots as you rotate about the y. This means if you rotate 180 about the y, rotation about the x is opposite your finger motion. The method also restricts motion to two degrees of freedom. The method linked to below, even though it doesn't directly affect the z-axis, somehow seems to allow three degrees of freedom. It also makes all vertical swipes rotate about the x in the logical direction.
How to rotate object in a scene with pan gesture - SceneKit
The way you set up your two gesture recognizers is identical, they will both fire for the same events (which is why the last one to be added predominates). There is no control within pan to specifically limit it to vertical or horizontal pans. Instead, consider analyzing the direction of the pan and then decide whether to rotate your gesture one way or the other, based upon which is greater.

Scene Kit SCNMorpher not working with primitives like SCNSphere etc

Has anybody got the SCNMorpher to work? If so, what exactly have you done?
I'm down now to a small test program, that should show a red cone and when I tap on the screen it should (should!) morphe that into a sphere/ball. But the only thing that happens is, that the first geometry goes away (get very small) and the second one (Morphers first target) never appears.
I must miss something very simple. Can anybody get me on the horse, please?
import SceneKit
private let DISTANCE_FAKTOR = CGFloat(sqrt(3.0)/2.0)
class GameViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let (scene, view) = configScene(self.view.frame)
self.view = view
// Position camera to see picture full screen and light at same position
let pov = SCNVector3(x: 0.0, y: 0.0, z: Float(max(view.frame.width, view.frame.height) * DISTANCE_FAKTOR))
let pol = SCNVector3(x: 0.0, y: 0.0, z: Float(max(view.frame.width, view.frame.height) * DISTANCE_FAKTOR))
scene.rootNode.addChildNode(makeShapes()) // Create and add background plane
scene.rootNode.addChildNode(makeCamera(pov)) // Add camera to scene
scene.rootNode.addChildNode(makeLight(pol)) // Add light to scene
// add a tap gesture recognizer
view.gestureRecognizers = [UITapGestureRecognizer(target: self, action: "handleTap:")]
}
func handleTap(gestureRecognize: UIGestureRecognizer) {
NSLog("Tapped")
if let effectNode = (view as? SCNView)?.scene?.rootNode.childNodeWithName("EFFECT", recursively: true) {
NSLog("Animating")
animate(effectNode)
}
}
func makeShapes() -> SCNNode {
// First shape is a cone
let torus = SCNCone(topRadius: 0.0, bottomRadius: view.frame.width/2.0, height: view.frame.width/4.0)
torus.firstMaterial = SCNMaterial()
torus.firstMaterial?.diffuse.contents = UIColor.redColor()
// Now an additional sphere/ball
let sphere = SCNSphere(radius: view.frame.width/2.0)
sphere.firstMaterial = SCNMaterial()
sphere.firstMaterial?.diffuse.contents = UIColor.greenColor()
// Put all in the node
let node = SCNNode()
node.geometry = torus
node.morpher = SCNMorpher()
node.morpher?.targets = [sphere]
node.name = "EFFECT"
// I would expect now something between a ball and a torus in red/green
node.morpher?.setWeight(0.5, forTargetAtIndex: 0)
return node
}
func animate(node: SCNNode) {
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(5.0)
node.morpher?.setWeight(1.0, forTargetAtIndex: 0) // From torus to ball
SCNTransaction.setCompletionBlock {
NSLog("Transaction completing")
SCNTransaction.begin()
SCNTransaction.setAnimationDuration(2.5)
node.morpher?.setWeight(0.0, forTargetAtIndex: 0) // And back
SCNTransaction.commit()
}
SCNTransaction.commit()
}
func configScene(frame: CGRect) -> (scene: SCNScene, view: SCNView) {
let view = SCNView()
view.frame = frame
view.autoresizingMask = UIViewAutoresizing.allZeros
view.backgroundColor = UIColor.blueColor()
let scene = SCNScene()
view.scene = scene
return (scene, view)
}
func makeCamera(pov: SCNVector3) -> SCNNode {
let camera = SCNCamera()
camera.zFar = Double(pov.z)
let node = SCNNode()
node.position = pov
node.camera = camera
return node
}
func makeLight(pol: SCNVector3) -> SCNNode {
let light = SCNLight()
light.type = SCNLightTypeOmni
light.zFar = CGFloat(pol.z)
let node = SCNNode()
node.position = pol
node.light = light
return node
}
override func shouldAutorotate() -> Bool {
return true
}
override func prefersStatusBarHidden() -> Bool {
return true
}
override func supportedInterfaceOrientations() -> Int {
if UIDevice.currentDevice().userInterfaceIdiom == .Phone {
return Int(UIInterfaceOrientationMask.AllButUpsideDown.rawValue)
} else {
return Int(UIInterfaceOrientationMask.All.rawValue)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Release any cached data, images, etc that aren't in use.
}
}
When I exchange the ball and the cone in the code, I can see the ball and it disappears when tapping the screen, but the cone never appears.
If you want to run this code, just create a new GameScene project in Xcode and copy this code into GameViewController.swift
Has anybody got the SCNMorpher to work?
Yes, I've gotten SCNMorpher to work between both custom geometry and geometry that has been loaded from a file.
The piece that you are missing is that all the morph targets need to have the same number of vertices and the vertices need to have the same arrangement. This is discussed in the documentation for the targets property:
The base geometry and all target geometries must be topologically identical — that is, they must contain the same number and structural arrangement of vertices.
Your example (morning between a sphere and a cone) doesn't fulfill this requirement and is not expected to work.
You can try and morph between two different spheres (with the same number of segments!) to see that it does work.

Resources