How to Create fall effect in Xcode ArKit - xcode

I m trying to create an effect that dice thrown by hand when I tapped to screen or shake. in my reference code there is rotation effect so that dice faces changes randomly. Im trying to make this code while dice is rotating it must be fall. I mean it will fall from a predefined coordinate y to coordinate y 0. Any idea will be apriciated.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if let touch = touches.first{
let touchLocation = touch.location(in: sceneView)
let results = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
if let hitResult = results.first{
let diceScene = SCNScene(named: "art.scnassets/diceCollada.scn")!
if let diceNode = diceScene.rootNode.childNode(withName: "Dice", recursively: false){
diceNode.position = SCNVector3(
hitResult.worldTransform.columns.3.x,
hitResult.worldTransform.columns.3.y + diceNode.boundingSphere.radius,
hitResult.worldTransform.columns.3.z)
let randomX = Float(arc4random_uniform(4) + 1) * (Float.pi/2)
let randomZ = Float(arc4random_uniform(4) + 1) * (Float.pi/2)
diceNode.runAction(
SCNAction.rotateBy(x: CGFloat(randomX * 3),
y: 0,
z: CGFloat(randomZ * 3),
duration: 0.5)
)
sceneView.scene.rootNode.addChildNode(diceNode)
}
print(hitResult)
}
}
}

Related

How to show WorldOrigin axis in SceneKit scene for 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.

Calculating the distance CGPoint travelled from one place to another

What method do I have to use for calculating the distance that CGPoint travelled from its old position to new position?
var point: CGPoint = CGPointMake(x:50.0, y:50.0)
...and there is a method for dragging a point with LMB:
func mouseDragged(event: NSEvent) {
var pointDragged = self.convertPoint(event.locationInWindow, fromView: nil)
}
Use Pythagorean theorem and mouseLocation = event.mouseLocation() where event in your case is of type NSEvent.
let point1: CGPoint = CGPoint(x: 2.0, y: 9.0)
let point2: CGPoint = CGPoint(x: 4.0, y: 13.0)
let xDist = (point1.x - point2.x)
let yDist = (point1.y - point2.y)
let distance = sqrt((xDist * xDist) + (yDist * yDist))
print(distance)
point1 would be your starting position and you can obtain the position where the user clicked - point2, from the NSEvent in your mouseDragged function:
mouseLocation = event.mouseLocation()
point2 = CGPointMake(mouseLocation.x, mouseLocation.y)

Getting Pixel Color from an Image using CGPoint in Swift 3

I am try this PixelExtractor class in Swift 3, get a error;
Cannot invoke initializer for type 'UnsafePointer' with an argument list of type '(UnsafeMutableRawPointer?)'
class PixelExtractor: NSObject {
let image: CGImage
let context: CGContextRef?
var width: Int {
get {
return CGImageGetWidth(image)
}
}
var height: Int {
get {
return CGImageGetHeight(image)
}
}
init(img: CGImage) {
image = img
context = PixelExtractor.createBitmapContext(img)
}
class func createBitmapContext(img: CGImage) -> CGContextRef {
// Get image width, height
let pixelsWide = CGImageGetWidth(img)
let pixelsHigh = CGImageGetHeight(img)
let bitmapBytesPerRow = pixelsWide * 4
let bitmapByteCount = bitmapBytesPerRow * Int(pixelsHigh)
// Use the generic RGB color space.
let colorSpace = CGColorSpaceCreateDeviceRGB()
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
let bitmapData = malloc(bitmapByteCount)
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedFirst.rawValue)
let size = CGSizeMake(CGFloat(pixelsWide), CGFloat(pixelsHigh))
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
// create bitmap
let context = CGBitmapContextCreate(bitmapData, pixelsWide, pixelsHigh, 8,
bitmapBytesPerRow, colorSpace, bitmapInfo.rawValue)
// draw the image onto the context
let rect = CGRect(x: 0, y: 0, width: pixelsWide, height: pixelsHigh)
CGContextDrawImage(context, rect, img)
return context!
}
func colorAt(x x: Int, y: Int)->UIColor {
assert(0<=x && x<width)
assert(0<=y && y<height)
let uncastedData = CGBitmapContextGetData(context)
let data = UnsafePointer<UInt8>(uncastedData)
let offset = 4 * (y * width + x)
let alpha: UInt8 = data[offset]
let red: UInt8 = data[offset+1]
let green: UInt8 = data[offset+2]
let blue: UInt8 = data[offset+3]
let color = UIColor(red: CGFloat(red)/255.0, green: CGFloat(green)/255.0, blue: CGFloat(blue)/255.0, alpha: CGFloat(alpha)/255.0)
return color
}
}
Fix this error.
let data = UnsafePointer<UInt8>(uncastedData)
->
let data = UnsafeRawPointer(uncastedData)
Get other error; 'Type 'UnsafeRawPointer?' has no subscript members'
How to modify this error?
You can write something like this when you have an UnsafeRawPointer in your data:
let alpha = data.load(fromByteOffset: offset, as: UInt8.self)
let red = data.load(fromByteOffset: offset+1, as: UInt8.self)
let green = data.load(fromByteOffset: offset+2, as: UInt8.self)
let blue = data.load(fromByteOffset: offset+3, as: UInt8.self)
Or else, you can get UnsafeMutablePointer<UInt8> from your uncastedData (assuming it's an UnsafeMutableRawPointer):
let data = uncastedData.assumingMemoryBound(to: UInt8.self)
SWIFT 3 (updated March 2017) Xcode 8 / IOS 10
Important: note that return value corresponds to red: b, green:r and blue: r as in the data they are stored backwards
First, create the extension (you can copy&paste somewhere in your
code)
extension UIImage {
func getPixelColor(pos: CGPoint) -> UIColor {
if let pixelData = self.cgImage?.dataProvider?.data {
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelInfo: Int = ((Int(self.size.width) * Int(pos.y)) + Int(pos.x)) * 4
let r = CGFloat(data[pixelInfo+0]) / CGFloat(255.0)
let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
let b = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
return UIColor(red: b, green: g, blue: r, alpha: a)
} else {
//IF something is wrong I returned WHITE, but change as needed
return UIColor.white
}
}
}
Then just call it as:
let colorAtPixel : UIColor = (theView.image?.getPixelColor(pos: CGPoint(x: 2, y: 2)))!
Although the code returns de exact color, it seems that is not returning the correct one for different CGPoints.
Might it be because of the screen resolution? (x1,x2,x3)?
It would be great if someone can add some light to the mystery...
Swift-3 (IOS 10.3)
extension UIImage {
func getPixelColor(atLocation location: CGPoint, withFrameSize size: CGSize) -> UIColor {
let x: CGFloat = (self.size.width) * location.x / size.width
let y: CGFloat = (self.size.height) * location.y / size.height
let pixelPoint: CGPoint = CGPoint(x: x, y: y)
let pixelData = self.cgImage!.dataProvider!.data
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
let pixelIndex: Int = ((Int(self.size.width) * Int(pixelPoint.y)) + Int(pixelPoint.x)) * 4
let r = CGFloat(data[pixelIndex]) / CGFloat(255.0)
let g = CGFloat(data[pixelIndex+1]) / CGFloat(255.0)
let b = CGFloat(data[pixelIndex+2]) / CGFloat(255.0)
let a = CGFloat(data[pixelIndex+3]) / CGFloat(255.0)
return UIColor(red: r, green: g, blue: b, alpha: a)
}
}
Usage : -
let color = yourImageView.image!.getPixelColor(atLocation: location, withFrameSize: yourImageView.frame.size)
location is a CGPoint
and size is size of your imageView
The following section is taken from some Swift 3 code I'm using to sample pixels from an image to get the predominant hue which I use to generate a background for tableView rows. The mechanics for the hue selection process don't apply to your question, so I'm just providing the relevant fragment.
let colorSpace = CGColorSpaceCreateDeviceRGB() // UIExtendedSRGBColorSpace
let newImage = image.cgImage?.copy(colorSpace: colorSpace)
let pixelData = newImage?.dataProvider!.data
let data: UnsafePointer<UInt8> = CFDataGetBytePtr(pixelData)
var hueFrequency = [Int: Double]()
hueFrequency[1] = 1 // Add one entry so this serves as a default if no hues from the image pass the filters
let nStart = 1
let mStart = 1
for n in nStart...Int(image.size.width / samplingFactor) {
for m in mStart...Int(image.size.height / samplingFactor) {
let pixelInfo: Int = ((Int(image.size.width) * m * Int(samplingFactor)) + n * Int(samplingFactor)) * 4 // bytesPerPixel
let b = CGFloat(data[pixelInfo]) / CGFloat(255.0) // cgImage bitmapinfo = rawValue 8194 -> BGRA ordering
let g = CGFloat(data[pixelInfo+1]) / CGFloat(255.0)
let r = CGFloat(data[pixelInfo+2]) / CGFloat(255.0)
let a = CGFloat(data[pixelInfo+3]) / CGFloat(255.0)
Also, note that I found the bitmapInfo value (image.cgImage!.bitmapInfo using my parameters) indicated a reordering of the RGBA sequence to BGRA, which I had to deal with in ordering the steps to pick out the data. If your colors are off, you may want to check this.

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.

Move SKSpriteNode until touches ended

I created this game in Spite Kit in Swift. I create a ship that I can determine its location be touching above or underneath the location of the ship. The ship will move towards the Y axis where I placed my finger. However at this point the ship will only move by a specific amount, in this case 30 or -30. How can I rewrite this code so the ship will keep on moving until my release of my finger?
Thanks!
func addShip() {
// Initializing spaceship node
ship = SKSpriteNode(imageNamed: "spaceship")
ship.setScale(0.45)
ship.zRotation = CGFloat(-M_PI/2)
// Adding SpriteKit physics body for collision detection
ship.physicsBody = SKPhysicsBody(rectangleOfSize: ship.size)
ship.physicsBody?.categoryBitMask = UInt32(shipCategory)
ship.physicsBody?.affectedByGravity = false
ship.physicsBody?.dynamic = true
ship.physicsBody?.contactTestBitMask = UInt32(obstacleCategory)
//NEWLY ADDED
ship.physicsBody?.contactTestBitMask = coinCategory
ship.physicsBody?.collisionBitMask = 0
ship.name = "ship"
ship.position = CGPointMake(120, 160)
self.addChild(ship)
actionMoveUp = SKAction.moveByX(0, y: 30, duration: 0.2)
actionMoveDown = SKAction.moveByX(0, y: -30, duration: 0.2)
}
i don't like to use SKActions for these types of things because they're really meant for one off simple animations.. However, it may be appropriate depending your game's requirements..
if you insist on using SKActions then instead of doing moveBy, why don't you just use moveTo utilizing touch location? The only thing you'd have to do is come up with a ratio based on the distance your sprite needs to travel so that your animation duration is correct. example:
let curPoint = self.sprite.position
let destinationPoint = touch.position
let diffPoint = CGPoint(
x: curPoint.x - destinationPoint.x,
y: curPoint.y - destinationPoint.y
)
let distance = sqrt(pow(diffPoint.x, 2) + pow(diffPoint.y, 2))
let durationRatio = distance / CGFloat(600)
let moveAction = SKAction.moveTo(point, duration: NSTimeInterval(durationRatio))
self.sprite.runAction(moveAction, withKey: "move")
then when you want to cancel the movement. in touchesEnded you do
self.sprite.removeActionForKey("move")
using physics velocity
let curPoint = CGPoint(x: 10, y: 10)
let destinationPoint = CGPoint(x: 100, y: 100)
let diffPoint = CGPoint(
x: curPoint.x - destinationPoint.x,
y: curPoint.y - destinationPoint.y
)
let distance = sqrt(pow(diffPoint.x, 2) + pow(diffPoint.y, 2))
let normalizedVector = CGPoint(
x: diffPoint.x / distance,
y: diffPoint.y / distance
)
let speed = CGFloat(100)
let velocity = CGVector(
dx: normalizedVector.x * speed,
dy: normalizedVector.y * speed
)
self.sprite.physicsBody!.velocity = velocity
in touchesEnded you do
self.sprite.physicsBody!.velocity = CGVector(dx: 0.0, dy: 0.0)

Resources