How to show WorldOrigin axis in SceneKit scene for macOS? - macos

I am building a macOS SwiftUI app. I want to show the world axis such that the user is aware of the orientation of objects. I've looked at the documentation, but the showWorldOrigin debug setting is not available on macOS. Is there an alternative way to show the world axis that I am missing?
While I've found external libraries that create a world axis and add nodes to the scene, I was hoping there was a built-in method to simplify the task and reduce any error.

You can create your own sample of procedural world axis for macOS 3D app.
SwiftUI mac version
import SwiftUI
import SceneKit
struct ContentView : View {
#State private var scene = SCNScene()
#State private var axis = SCNNode()
var options: SceneView.Options = [.allowsCameraControl]
var body: some View {
ZStack {
SceneView(scene: scene, options: options).ignoresSafeArea()
let _ = scene.background.contents = NSColor.black
let _ = createWorldAxis()
let _ = axis.opacity = 0.1 // you can hide world axis
}
}
func createWorldAxis() {
let colors: [NSColor] = [.systemRed, .systemGreen, .systemBlue]
for index in 0...2 {
let box = SCNBox(width: 0.200, height: 0.005,
length: 0.005, chamferRadius: 0.001)
let material = SCNMaterial()
material.lightingModel = .constant
material.diffuse.contents = colors[index]
box.materials[0] = material
let node = SCNNode(geometry: box)
switch index {
case 0:
node.position.x += 0.1
case 1:
node.eulerAngles = SCNVector3(0, 0, Float.pi/2)
node.position.y += 0.1
case 2:
node.eulerAngles = SCNVector3(0, -Float.pi/2, 0)
node.position.z += 0.1
default: break
}
axis.addChildNode(node)
axis.scale = SCNVector3(1.5, 1.5, 1.5)
scene.rootNode.addChildNode(axis)
}
print(axis.position)
}
}
Cocoa version
import Cocoa
import SceneKit
class ViewController : NSViewController {
var axis = SCNNode()
var sceneView = SCNView()
override func viewDidLoad() {
super.viewDidLoad()
sceneView = self.view as! SCNView
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.backgroundColor = .black
self.createWorldAxis()
axis.opacity = 0.1 // you can hide world axis
}
func createWorldAxis() {
let colors: [NSColor] = [.systemRed, .systemGreen, .systemBlue]
for index in 0...2 {
let box = SCNBox(width: 0.200, height: 0.005,
length: 0.005, chamferRadius: 0.001)
let material = SCNMaterial()
material.lightingModel = .constant
material.diffuse.contents = colors[index]
box.materials[0] = material
let node = SCNNode(geometry: box)
if index == 0 {
node.position.x += 0.1
} else if index == 1 {
node.eulerAngles = SCNVector3(0, 0, Float.pi/2)
node.position.y += 0.1
} else if index == 2 {
node.eulerAngles = SCNVector3(0, -Float.pi/2, 0)
node.position.z += 0.1
}
axis.addChildNode(node)
axis.scale = SCNVector3(1.5, 1.5, 1.5)
sceneView.scene?.rootNode.addChildNode(axis)
}
print(axis.position)
}
}

Posting this answer, as per OP comments...
Apple's docs list the following SCNDebugOptions:
.showPhysicsShapes
.showBoundingBoxes
.showLightInfluences
.showLightExtents
.showPhysicsFields
.showWireframe
.renderAsWireframe
.showSkeletons
.showCreases
.showConstraints
.showCameras
.showFeaturePoints
.showWorldOrigin
Curiously, the last two - .showFeaturePoints and .showWorldOrigin - are not defined in SceneKit. And the discussion notes refer only to ARKit, where they are defined.
The docs for SCNDebugOptions state that these are bit mask patterns ... and if we print them out, we get:
showPhysicsShapes: SCNDebugOptions(rawValue: 1)
showBoundingBoxes: SCNDebugOptions(rawValue: 2)
showLightInfluences: SCNDebugOptions(rawValue: 4)
showLightExtents: SCNDebugOptions(rawValue: 8)
showPhysicsFields: SCNDebugOptions(rawValue: 16)
showWireframe: SCNDebugOptions(rawValue: 32)
renderAsWireframe: SCNDebugOptions(rawValue: 64)
showSkeletons: SCNDebugOptions(rawValue: 128)
showCreases: SCNDebugOptions(rawValue: 256)
showConstraints: SCNDebugOptions(rawValue: 512)
showCameras: SCNDebugOptions(rawValue: 1024)
So... we try this to get "the next one in order" (expecting it to equate to .showFeaturePoints):
sceneView.debugOptions = SCNDebugOptions(rawValue: 2048)
Turns out, that gives us RGB axis indicators ... the .showWorldOrigin.
For a simple scene with an extruded bezier path (a cube), using these options:
sceneView.debugOptions = [.renderAsWireframe, SCNDebugOptions(rawValue: 2048)]
we get this output:
Trying one further, thinking maybe we get .showFeaturePoints:
sceneView.debugOptions = SCNDebugOptions(rawValue: 4096)
doesn't seem to do anything - at least, I don't see any visual change in my simple Scene.

I found what i wanted using the UI. First, ensure you enable controls for your scene:
myscene.showsStatistics = true
Then, click the configuration button on the bottom of your screen.
In the options dropdown select World Origin.
I am puzzled why all debug options can be invoked programmatically except the World Origin. None the less, that allows you to see axis.

Related

How to Animate a UIImage out of the Screen in Swift

i have searched around the web but nothing could answer my question.
I am trying to animate an UIImageview out of the Screen, set the alpha of it to 0, reset its position and reset the alpha to 1 again.
I can't seem to get the right position where to animate the Image to.
I have set up a gesturerecognizer to drag the image around and when the center of the image is above a certain point, i want to animate it out of screen.
This is the Regocnizercode :
#objc func imageDragged(gestureRecognizer: UIPanGestureRecognizer){
//Current Point where the label is dragged
let draggedLabelPoint = gestureRecognizer.translation(in: view)
//Updating the center of the label : viewcenter +/- currentpoint
matchImageView.center = CGPoint(x: view.bounds.width/2 + draggedLabelPoint.x, y: view.bounds.height/2 + draggedLabelPoint.y)
let xFromCenter = view.bounds.width/2 - matchImageView.center.x
var rotation = CGAffineTransform(rotationAngle: xFromCenter / -500)
let scale = min(100/abs(xFromCenter),1)
var scaledAndRotated = rotation.scaledBy(x: scale, y: scale)
//Transforming the Item respective to the distance from center
matchImageView.transform = scaledAndRotated
if gestureRecognizer.state == .ended {
if matchImageView.center.x < (view.bounds.width/2 - 100) {
animateImageToSide(target: CGPoint(x: view.bounds.width - 200, y: matchImageView.center.y))
matchImageView.alpha = 0
}
if matchImageView.center.x > (view.bounds.width/2 + 100) {
animateImageToSide(target: CGPoint(x: view.bounds.width + 200, y: matchImageView.center.y))
matchImageView.alpha = 0
}
//Reset the scaling variables and recentering the Item after swipe
rotation = CGAffineTransform(rotationAngle: 0)
scaledAndRotated = rotation.scaledBy(x: 1, y: 1)
matchImageView.transform = scaledAndRotated
matchImageView.center = CGPoint(x: view.bounds.width/2, y: view.bounds.height/2)
}
}
I am sorry if the resetting part is irrelevant for you, but im not sure if this could cause this behavior.
And this is my current Animationmethod :
func animateImageToSide(target:CGPoint){
UIImageView.animate(withDuration: 0.5, delay: 0, options: .curveLinear, animations: {self.matchImageView.center = target}) { (success: Bool) in
self.updateImage()
self.fadeInImage()//This is where i set the alpha back to 1
}
}
I am not sure if a CGPoint is what i need to use.
The current behavoir of the image is very weird. Most of the time it snaps to a specific place, regardless of the targetposition. Maybe my attempt on this is entirely wrong.
Thanks for your help !

SceneKit: orient one node toward another in 3Dspace

I need to orient one node to point its Z-axis at another node in 3D. Yeah, the perfect job for the LookAtConstraint. And for most of my work LookAt is fine. But when I apply LookAt to a particular node, I can no longer animate that node's translation with SCNAction. Picture a hydrogen atom leaving a molecule as it ionizes. The orientation is needed to properly rotate the bond (a cylinder) bewteen the hydrogen and an oxygen atom on the molecule.
I can orient the bond FROM the oxygen TO the hydrogen and animate. But this disorients most of the other bonds which were getting by just fine with LookAt's.
I gave this a mighty try before realizing it answers a somewhat different question:
Calculate rotations to look at a 3D point?
I had a similar issue with a project. What I eventually realized was that I need to use multiple constraints. One for translation (movement) and the other using the look at constraint.
I would move the object and then apply the look at constraint; in this case, it was a camera following an objects being moved using actions. Code snippet follows:
let targetNodeConstraint = SCNLookAtConstraint(target: someObject)
targetNodeConstraint.gimbalLockEnabled = true
let followObjectConstraint = SCNTransformConstraint(inWorldSpace: true, withBlock: { (node, matrix) -> SCNMatrix4 in
let transformMatrix = SCNMatrix4MakeTranslation(
self.someObject.position.x - 1.0,
self.someObject.position.y, self.someObject.position.z + 1.0)
return transformMatrix
})
// Position the object behind the other object & rotate it to
roadCamera.constraints = [followObjectConstraint, targetNodeConstraint]
The important thing to note is the order in which the constraints are added to the object using an array. In the code above, I am ignoring the current matrix before I apply a transform matrix (I should re-write this code someday)
The complete source code of this "experiment" is on GitHub as I try things out.
https://github.com/ManjitBedi/CubeTrip
Hopefully, this is helpful.
My solution here. Deal with situation that node continuously translate in space and should always toward a position.
#discardableResult
func yew(_ node:SCNNode, toPosition position:SCNVector3) -> Float
{
var eularAngle = SCNVector3Zero
let tranform = node.transform
var forward = GLKVector3Make(tranform.m31, tranform.m32, tranform.m33)
var toWard = GLKVector3Make(position.x - node.position.x, position.y - node.position.y, position.z - node.position.z)
forward = GLKVector3Normalize(GLKVector3Make(forward.x, 0, forward.z))
toWard = GLKVector3Normalize(GLKVector3Make(toWard.x, 0, toWard.z))
var dotProduct = GLKVector3DotProduct(forward,toWard)
dotProduct = (dotProduct > 1) ? 1 : ((dotProduct < -1) ? -1 : dotProduct)
var yew = acos(dotProduct)
if yew < 0 {
assert(false)
}
//toward is clockwise of forward
let isCW = GLKVector3CrossProduct(forward, toWard).y < 0
if isCW {
yew = -yew
}
eularAngle.y = yew
node.eulerAngles = SCNVector3Make(eularAngle.x + wrapperNode.eulerAngles.x,
eularAngle.y + wrapperNode.eulerAngles.y,
eularAngle.z + wrapperNode.eulerAngles.z)
return yew
}
#discardableResult
func pitch(_ node:SCNNode, toPosition position:SCNVector3) -> Float{
var eularAngle = SCNVector3Zero
let tranform = node.transform
var toWard = GLKVector3Make(position.x - node.position.x, position.y - node.position.y, position.z - node.position.z)
var forward = GLKVector3Make(tranform.m31, tranform.m32, tranform.m33)
forward = GLKVector3Normalize(forward)
toWard = GLKVector3Normalize(toWard)
var dotProduct = GLKVector3DotProduct(forward,toWard)
dotProduct = (dotProduct > 1) ? 1 : ((dotProduct < -1) ? -1 : dotProduct)
var pitch = acos(dotProduct)
//toward is clockwise of forward, if right vector of model and crossProfuct.x has same direction
let crossProduct = GLKVector3CrossProduct(forward, toWard)
let isCW = (crossProduct.x <= 0) != (tranform.m11 <= 0)
if isCW {
pitch = -pitch
}
eularAngle.x = pitch
node.eulerAngles = SCNVector3Make(eularAngle.x + node.eulerAngles.x,
eularAngle.y + node.eulerAngles.y,
eularAngle.z + node.eulerAngles.z)
return pitch
}
func orient(_ node:SCNNode, toPosition position:SCNVector3) {
self.yew(node, toPosition: position)
self.pitch(node, toPosition: position)
}

during Spritenode animation and movement appear red large x

Here is problem in which I add zoombie sprite to the scene every one second. When I add another sub animated zoombie to the zoombie node, sometimes it loads animated texture, and other times appear red large X.
func addMonster() {
let zoombieSprite = SKSpriteNode(color: SKColor.greenColor(), size: CGSizeMake(40, 60))
// Determine where to spawn the monster along the Y axis
let actualY = randRange(lower: zoombieSprite.size.height, upper: size.height - zoombieSprite.size.height)
// Position the monster slightly off-screen along the right edge,
// and along a random position along the Y axis as calculated above
zoombieSprite.position = CGPoint(x: size.width + zoombieSprite.size.width/2, y: actualY)
zoombieSprite.physicsBody = SKPhysicsBody(rectangleOfSize: zoombieSprite.size) // 1
zoombieSprite.physicsBody?.dynamic = true // 2
zoombieSprite.physicsBody?.categoryBitMask = PhysicsCategory.Monster // 3
zoombieSprite.physicsBody?.contactTestBitMask = PhysicsCategory.Projectile // 4
zoombieSprite.physicsBody?.collisionBitMask = PhysicsCategory.None // 5
addChild(zoombieSprite)
//zoombieSprite.addChild(createAnimatedZoombie())
let zoombieAnimation = SKAction.runBlock({
zoombieSprite.addChild(self.createAnimatedZoombie())
})
// Determine speed of the monster
let actualDuration = randRange(lower: 6.0, upper: 10.0)
//print("actualDuration = \(actualDuration)")
let actionMove = SKAction.moveTo(CGPoint(x: -zoombieSprite.size.width/2, y: actualY), duration: NSTimeInterval(actualDuration))
// Create the actions
let actionMoveDone = SKAction.removeFromParent()
zoombieSprite.runAction(SKAction.sequence([zoombieAnimation ,actionMove,actionMoveDone]))
}
//MARK: - ANIMATE FRAME AND MOVE ZOOMBIE
func createAnimatedZoombie () -> SKSpriteNode {
let animatedZoobieNode = SKSpriteNode(texture: spriteArray[0])
let animationFrameAction = SKAction.animateWithTextures(spriteArray, timePerFrame: 0.2)
let durationTime = SKAction.waitForDuration(0.1)
let repeatAction = SKAction.repeatActionForever(animationFrameAction)
let quenceAction = SKAction.sequence([durationTime, repeatAction])
animatedZoobieNode.runAction(quenceAction)
return animatedZoobieNode
}
Thanks very much my respectable brother Joseph Lord and Thank God i solved my problem by just dividing sprite kit atlas array count property by 2 because in this folder i had put both #2x and #3x images so when i used to get number of images from this atlas folder it used to return the number which was addition of #2x and #3x images.

Multiple Contacts Issue in SpriteKit Game

In my game, I have a ball, walls and gaps between these walls. Ball and walls are SKSpriteNode, gap is SKNode. I use gap to count the score.
I have the following code to handle the contacts and collisions. I checked plenty of documents and tutorials but cant find why there are multiple gap contacts happen when my ball contacts the gap. It prints "gap contact" for 6 times every time.
Bodytypes definitions:
enum BodyType:UInt32 {
case ball = 1
case wall = 2
case gap = 4
}
Ball:
var ball = SKSpriteNode(texture: SKTexture(imageNamed: "img/ball.png"))
ball.position = CGPointMake(frame.width/2, 50)
ball.physicsBody = SKPhysicsBody(texture: ball.texture, size: ball.size)
ball.physicsBody?.dynamic = true
ball.physicsBody?.allowsRotation = false
ball.physicsBody?.categoryBitMask = BodyType.ball.rawValue
ball.physicsBody?.collisionBitMask = BodyType.wall.rawValue
ball.physicsBody?.contactTestBitMask = BodyType.gap.rawValue | BodyType.wall.rawValue
Walls (walls are created through scheduledTimerWithTimeInterval every 2 seconds):
var wall1 = SKSpriteNode(texture: nil, color: UIColor.blackColor(), size: CGSizeMake(frame.size.width, 20))
var wall2 = SKSpriteNode(texture: nil, color: UIColor.blackColor(), size: CGSizeMake(frame.size.width, 20))
let gapSize = 70
let gapMargin = 50
let gapPoint = CGFloat(arc4random_uniform(UInt32(frame.size.width) - gapSize - (gapMargin*2)) + gapMargin)
wall1.position = CGPointMake(-wall1.size.width/2 + gapPoint, self.frame.size.height)
wall2.position = CGPointMake(wall1.size.width/2 + CGFloat(gapSize) + gapPoint, self.frame.size.height)
wall1.physicsBody = SKPhysicsBody(rectangleOfSize: wall1.size)
wall1.physicsBody?.dynamic = false
wall2.physicsBody = SKPhysicsBody(rectangleOfSize: wall2.size)
wall2.physicsBody?.dynamic = false
wall1.physicsBody?.categoryBitMask = BodyType.wall.rawValue
wall2.physicsBody?.categoryBitMask = BodyType.wall.rawValue
Gap Node:
var gap = SKNode()
gap.physicsBody = SKPhysicsBody(rectangleOfSize: CGSizeMake(CGFloat(gapSize), wall1.size.height))
gap.position = CGPointMake(CGFloat(gapSize)/2 + gapPoint, self.frame.size.height)
gap.runAction(moveAndRemoveWall)
gap.physicsBody?.dynamic = false
gap.physicsBody?.collisionBitMask = 0
gap.physicsBody?.categoryBitMask = BodyType.gap.rawValue
Contact Handling:
func didBeginContact(contact: SKPhysicsContact) {
let contactMask = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
switch(contactMask) {
case BodyType.ball.rawValue | BodyType.gap.rawValue:
println("gap contact")
default:
println("wall contact")
movingObjects.speed = 0
explosion(ball.position)
gameOver = 1
}
}
Wall collision works perfectly and prints "wall contact" only once when I hit the wall but I couldnt fix this multiple contact issue between the ball and gap.
UPDATE: The problem is with the ball's physicsbody. If I use circleOfRadius it works perfectly but then, this doesnt provide a great experience to the player as my ball shape is not a perfect circle so I want to use the texture to cover the physicsbody.

Repeating a Background Image in Swift

Im using swift programming language in xcode and in spritekit. I want to get my background image to scroll forever. I have this code so far but when I run it in the simulator it goes through only one time and goes back to the grey background. How do I get it so that the background repeats forever. I already got the background to move its just that I want it to repeat forever.
class GameScene: SKScene, SKPhysicsContactDelegate {
var background = SKSpriteNode()
// Background
background = SKSpriteNode(imageNamed: "background")
background.position = CGPointMake(self.size.width/2, self.size.height/2)
addChild(background)
// Repeat Background Image Forever
moveForeverAction()
}
//loop background image infinitely
func moveForeverAction() {
let moveNode = SKAction.moveByX(0, y: background.size.height * 2.0 , duration: NSTimeInterval(0.01 * background.size.height * 2.0))
let resetPosition = SKAction.moveByX(0, y: background.size.height * 2.0, duration: 0)
let moveNodeForever = SKAction.repeatActionForever(SKAction.sequence([moveNode, resetPosition]))
background.runAction(moveNodeForever)
}
It's because you're adding the same background.
add another declaration of bacground in the loop :
for var i:CGFloat=0; i < 3; ++i {
**background = SKSpriteNode(imageNamed: "background")**
background.position = CGPointMake(background.size.width / 2, i * background.size.height )
background.runAction(moveNodeForever)
}
You've to runAction() 3 times. For example :
for var i:CGFloat=0; i < 3; ++i {
background.position = CGPointMake(background.size.width / 2, i * background.size.height )
background.runAction(moveNodeForever)
}

Resources