SceneKit. How to create box with rounded edges and with different radiuses? - ios

I've started learning SceneKit. And I've tried SCNBox. And it has chamferRadius. But the radius is applied for all the edges.
But I want to achieve something similar to the one on the screenshot below

You can do this by extruding a UIBezierPath:
// rounded rect bezier path
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 1.0, height: 1.0), cornerRadius: 0.1)
path.flatness = 0
// extrude the path
let shape = SCNShape(path: path, extrusionDepth: 0.05)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor(white: 0.9, alpha: 1.0)
shape.materials = [mat]
let shapeNode = SCNNode(geometry: shape)
Result:
Here's a full example (note: I have only glanced at SceneKit, so I used this tutorial as a starting point Introduction to SceneKit):
import UIKit
import SceneKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
let scene = SCNScene()
sceneView.scene = scene
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 3.0, y: 2.0, z: 1.5)
let ambientLight = SCNLight()
ambientLight.type = .ambient
ambientLight.color = UIColor(white: 0.9, alpha: 1.0)
cameraNode.light = ambientLight
let light = SCNLight()
light.type = SCNLight.LightType.spot
light.spotInnerAngle = 30.0
light.spotOuterAngle = 80.0
light.castsShadow = true
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
// rounded rect bezier path
let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 1.0, height: 1.0), cornerRadius: 0.1)
path.flatness = 0
// extrude the path
let shape = SCNShape(path: path, extrusionDepth: 0.05)
let mat = SCNMaterial()
mat.diffuse.contents = UIColor(white: 0.9, alpha: 1.0)
shape.materials = [mat]
let shapeNode = SCNNode(geometry: shape)
let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
let planeNode = SCNNode(geometry: planeGeometry)
planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
planeNode.position = SCNVector3(x: 0, y: 0.0, z: 0)
let floorMaterial = SCNMaterial()
floorMaterial.diffuse.contents = UIColor.lightGray
planeGeometry.materials = [floorMaterial]
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(shapeNode)
scene.rootNode.addChildNode(planeNode)
let constraint = SCNLookAtConstraint(target: shapeNode)
constraint.isGimbalLockEnabled = true
cameraNode.constraints = [constraint]
lightNode.constraints = [constraint]
}
}

Related

How to use a SKScene as a SCNMaterial?

I am having a SKScene as a SCNMaterial that I want to apply on a SCNSphere. I want to add a point on the surface of the sphere, that should be partially visible even when it is on the other side of the sphere. So I want a double sided texture. But when I use spherematerial.isDoubleSided = true I get a whitish layer on top of my sphere. Not sure why this happens.
Here is my code:
let surfacematerial = SKScene(size: CGSize(width: 800, height: 400))
surfacematerial.backgroundColor = SKColor.blue
self.point.fillColor = SKColor.red
self.point.lineWidth = 0
self.point.position = CGPoint(x: 200, y: 200)
surfacematerial.addChild(point)
let spherematerial = SCNMaterial()
spherematerial.isDoubleSided = true
spherematerial.diffuse.contents = surfacematerial
let sphere = SCNSphere(radius: CGFloat(2))
sphere.materials = [spherematerial]
spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0, y: 0, z: 0)
spherenode.opacity = CGFloat(0.9)

SceneKit With Vuforia AR library custom models

I am new in SceneKit, working with swift 3 and latest Vuforia library 6-2-9.
I have a problem with applying textures on custom 3D models.
fileprivate func createDefaultMarkerScene(with view: VuforiaEAGLView) -> SCNScene {
let scene = SCNScene()
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.light?.color = UIColor.lightGray
lightNode.position = SCNVector3(x: 0, y: 0, z: 1000)
scene.rootNode.addChildNode(lightNode)
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
let geoNode = SCNNode()
geoNode.name = "model_model"
let normalsSources = SCNGeometrySource(normals: normalsSCNVector3)
let verticesSources = SCNGeometrySource(vertices: verticesSCNVector3)
let texCoordsSources = SCNGeometrySource(textureCoordinates: texCoordCGPoint)
let indicesElements = SCNGeometryElement(indices: indices, primitiveType: SCNGeometryPrimitiveType.triangles)
geoNode.geometry = SCNGeometry(sources: [verticesSources, normalsSources, texCoordsSources], elements: [indicesElements])
geoNode.position = SCNVector3(x: 0, y: 0, z: 0)
geoNode.scale = SCNVector3(x: 50, y: 50, z: 50)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "grad_ao.png")
geoNode.geometry?.firstMaterial = material
scene.rootNode.addChildNode(geoNode)
return scene
}
Model is properly rendered, but applied texture is completely messed up. I have tried with rotated image, also uv coordinates all ok [0..1].
Any ideas? Thank you
I have managed to fix an issue.
The key is in "texture.png" file.
I just need to flip the image for 180 degrees with:
material.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)

Improve SceneKit Playground Speed

I am testing some simple SceneKit runtime node creation of planes (only about 30 planes) and would prefer to use a Playground to test concepts. Normally Playgrounds run reasonably fast but with SceneKit the drawing that normally takes a fraction of a second is taking minutes. Here is my code which draws a simple 5x5 maze
import UIKit
import SceneKit
import PlaygroundSupport // needed to create the live view
let maze = Maze(mazeSizeX, mazeSizeY)
public let π = M_PI
maze.solveMaze(x: mazeSizeX-1, y: mazeSizeY-1, comingFrom: -1)
maze.displayWithSolution()
let cellSize: CGFloat = 1.0
let scene = SCNScene()
let blueMat = SCNMaterial()
blueMat.diffuse.contents = #colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)
blueMat.lightingModel = SCNMaterial.LightingModel.physicallyBased
let redMat = SCNMaterial()
redMat.diffuse.contents = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)
redMat.lightingModel = SCNMaterial.LightingModel.physicallyBased
let greenMat = SCNMaterial()
greenMat.diffuse.contents = #colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)
greenMat.lightingModel = SCNMaterial.LightingModel.physicallyBased
let purpleMat = SCNMaterial()
purpleMat.diffuse.contents = #colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)
purpleMat.lightingModel = SCNMaterial.LightingModel.physicallyBased
let planeN = SCNPlane(width: cellSize, height: cellSize)
let planeS = SCNPlane(width: cellSize, height: cellSize)
let planeE = SCNPlane(width: cellSize, height: cellSize)
let planeW = SCNPlane(width: cellSize, height: cellSize)
planeN.materials = [blueMat]
planeS.materials = [redMat]
planeE.materials = [greenMat]
planeW.materials = [purpleMat]
//plane.firstMaterial?.isDoubleSided = true
for x in 0 ..< mazeSizeX
{
let xPos: CGFloat = CGFloat(x)
for y in 0 ..< mazeSizeY
{
let yPos: CGFloat = CGFloat(y)
if maze.isWallThere(x: Double(x), y: Double(y), side: North)
{
let planeNode = SCNNode(geometry: planeN)
planeNode.rotation = SCNVector4(-1, 0, 0, π/2)
planeNode.position = SCNVector3(-yPos*cellSize, (xPos-0.5)*cellSize, cellSize/2.0)
scene.rootNode.addChildNode(planeNode)
}
if maze.isWallThere(x: Double(x), y: Double(y), side: South)
{
let planeNode = SCNNode(geometry: planeS)
planeNode.rotation = SCNVector4(1, 0, 0, π/2)
planeNode.position = SCNVector3(-yPos*cellSize, (xPos+0.5)*cellSize, cellSize/2.0)
scene.rootNode.addChildNode(planeNode)
}
if maze.isWallThere(x: Double(x), y: Double(y), side: East)
{
let planeNode = SCNNode(geometry: planeE)
planeNode.rotation = SCNVector4(0, 1, 0, π/2)
planeNode.position = SCNVector3((-yPos-0.5)*cellSize, xPos*cellSize, cellSize/2.0)
scene.rootNode.addChildNode(planeNode)
}
if maze.isWallThere(x: Double(x), y: Double(y), side: West)
{
let planeNode = SCNNode(geometry: planeW)
planeNode.rotation = SCNVector4(0, -1, 0, π/2)
planeNode.position = SCNVector3((-yPos+0.5)*cellSize, xPos*cellSize, cellSize/2.0)
scene.rootNode.addChildNode(planeNode)
}
}
}
let floor = SCNNode(geometry: SCNBox(width: CGFloat(mazeSizeX), height: CGFloat(mazeSizeY), length: 0.1, chamferRadius: 0))
floor.position = SCNVector3(-(CGFloat(mazeSizeX)/2.0)+0.5, (CGFloat(mazeSizeY)/2.0)-0.5, 0)
scene.rootNode.addChildNode(floor)
let light = SCNLight()
light.type = SCNLight.LightType.omni
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(40,12,15)
scene.rootNode.addChildNode(lightNode)
let light2 = SCNLight()
light2.type = SCNLight.LightType.omni
let lightNode2 = SCNNode()
lightNode2.light = light
lightNode2.position = SCNVector3(-40,-24,-30)
scene.rootNode.addChildNode(lightNode2)
var cameraPosition = SCNVector3Make(0.5, 0.5, 0.5)
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = cameraPosition
cameraNode.rotation = SCNVector4(-1, 0, 0, π/2)
scene.rootNode.addChildNode(cameraNode)
//let view = SCNView() //iPad version
let view = SCNView(frame: CGRect(x: 0, y: 0, width: 400, height: 600)) //Xcode version
view.allowsCameraControl = true
view.autoenablesDefaultLighting = true
view.showsStatistics = true
view.scene = scene
view.backgroundColor = #colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)
PlaygroundPage.current.liveView = view
Is there anything I am doing wrong or could do to optimize the SceneKit drawing in a Playground?
I do my SceneKit Playground work in macOS whenever possible. The performance just isn't there on iOS Playgrounds.

Spritekit - Rotate a SK3dNode along its y axis

I have 3D star in spritekit and have it rotating along z axis so it looks like its spinning.
However I cannot get it to spin.
The file is a .dae file which I have imported into my project.
The star itself loads but nothing happens. Any idea anyone?
import SpriteKit
override func didMove(to view: SKView) {
let starScene = SCNScene.init(named: "star.dae")
let star3d = SK3DNode(viewportSize: CGSize(width: 330, height: 330))
star3d.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
star3d.scnScene = starScene
star3d.zPosition = 3
star3d.isPlaying = true
star3d.scnScene = starScene
let camera = SCNCamera()
camera.xFov = 60
camera.yFov = 60
var cameraNode = SCNNode()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let rotationAnimation = CABasicAnimation(keyPath: "rotation")
rotationAnimation.toValue = NSValue(scnVector4: SCNVector4Make(1, 2, 3, .pi * 2))
rotationAnimation.duration = 6
rotationAnimation.repeatCount = FLT_MAX
starScene?.rootNode.addChildNode(cameraNode)
star3d.pointOfView = cameraNode
self.addChild(star3d)
cameraNode.addAnimation(rotationAnimation, forKey: nil)
}
You have correctly set star3d.pointOfView to the camera, but you haven't added cameraNodeto the scene.
starScene.rootNode.addChildNode(cameraNode)
Here's a complete version, in a macOS playground. Swap out the last two lines (current.liveView) to see the raw SceneKit render or the combined SpriteKit render.
import Cocoa
import SceneKit
import SpriteKit
import PlaygroundSupport
let spritekitView = SKView(frame: CGRect(x: 0, y: 0, width: 800, height: 500))
let scenekitView = SCNView(frame: CGRect(x: 0, y: 0, width: 800, height: 500))
let spriteKitScene = SKScene(size: CGSize(width: 480, height: 320))
spritekitView.showsFPS = true
spritekitView.presentScene(spriteKitScene)
PlaygroundSupport.PlaygroundPage.current.liveView = spritekitView
// let starScene = SCNScene.init(named: "star.dae")
let starScene = SCNScene()
starScene.rootNode.addChildNode(SCNNode(geometry: SCNBox(width: 1, height: 2, length: 3, chamferRadius: 0.2)))
let star3d = SK3DNode(viewportSize: CGSize(width: 330, height: 330))
star3d.position = CGPoint(x: spriteKitScene.frame.midX, y: spriteKitScene.frame.midY)
star3d.scnScene = starScene
star3d.isPlaying = true
spriteKitScene.addChild(star3d)
let camera = SCNCamera()
camera.xFov = 60
camera.yFov = 60
var cameraNode = SCNNode()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 3, z: 15)
starScene.rootNode.addChildNode(cameraNode)
star3d.pointOfView = cameraNode
scenekitView.scene = starScene
scenekitView.autoenablesDefaultLighting = true
scenekitView.pointOfView = cameraNode
let rotateVector = SCNVector4Make(0, 0, 1, CGFloat(2 * M_PI))
let rotate = SCNAction.repeatForever(SCNAction.rotateBy(x: 0.0, y: 0, z: CGFloat(2 * M_PI), duration: 10.0))
cameraNode.runAction(rotate)
let rotationAnimation = CABasicAnimation(keyPath: "rotation")
rotationAnimation.toValue = NSValue(scnVector4: rotateVector)
rotationAnimation.duration = 6
rotationAnimation.repeatCount = FLT_MAX
//cameraNode.addAnimation(rotationAnimation, forKey: nil)
PlaygroundPage.current.liveView = spritekitView
//PlaygroundPage.current.liveView = scenekitView
Thanks to Hal Mueller.
The following spinning star effect can be achieved with the below code adding a sk3dnode into spritekit.
media0.giphy.com/media/rOhH4D15SlWCc/giphy.gif
If the animation does not run on your device.
In your project .plist file add the following row.
PrefersOpenGL = YES
let starScene = SCNScene.init(named: "Round_CStar_obj.obj.dae")
let star3d = SK3DNode(viewportSize: CGSize(width: 300, height: 300))
star3d.position = CGPoint(x: self.frame.midX, y: self.frame.midY)
star3d.scnScene = starScene
star3d.name = "star3d"
star3d.zPosition = 3
star3d.zRotation = 6
star3d.isPlaying = true
star3d.scnScene = starScene
self.addChild(star3d)
let camera = SCNCamera()
camera.usesOrthographicProjection = true
camera.orthographicScale = 9
camera.zNear = 0
camera.zFar = 200
var cameraNode = SCNNode()
cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 0)
cameraNode.eulerAngles = SCNVector3(x: 0, y: 0, z: 0)
starScene?.rootNode.addChildNode(cameraNode)
star3d.pointOfView = cameraNode
let rotationAnimation = CABasicAnimation(keyPath: "rotation")
rotationAnimation.toValue = NSValue(scnVector4: SCNVector4Make(0, 1, 0, .pi * 2))
rotationAnimation.duration = 2
rotationAnimation.repeatCount = FLT_MAX
cameraNode.addAnimation(rotationAnimation, forKey: nil)

Scale SCNNode in SceneKit

I have the following SCNNode:
let box = SCNBox(width: 10.0, height: 10.0, length: 10.0, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3(x: 0, y: 0, z: 0)
If I apply:
boxNode.scale = SCNVector3(x: 0.5, y: 0.5, z: 0.5)
it appears to have no effect on the size of the box. I've checked this with boxNode.getBoundingBoxMin(&v1, max: &v2). It's always the same and appears the same on-screen.
Am I missing something? The docs imply that setting the scale should affect the node's geometry and thus be a different size.
Thanks.
J.
I just tested in a playground and it worked perfectly for me. Perhaps your camera is zooming in to compensate for the smaller box?
import SceneKit
import SpriteKit
import XCPlayground
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 800, height: 600))
XCPlaygroundPage.currentPage.liveView = sceneView
var scene = SCNScene()
sceneView.scene = scene
sceneView.backgroundColor = SKColor.greenColor()
sceneView.debugOptions = .ShowWireframe
// default lighting
sceneView.autoenablesDefaultLighting = true
// a camera
var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 15, y: 15, z: 30)
scene.rootNode.addChildNode(cameraNode)
let box = SCNBox(width: 10.0, height: 10.0, length: 10.0, chamferRadius: 0)
let boxNode = SCNNode(geometry: box)
boxNode.position = SCNVector3(x: 0, y: 0, z: 0)
scene.rootNode.addChildNode(boxNode)
let centerConstraint = SCNLookAtConstraint(target: boxNode)
cameraNode.constraints = [centerConstraint]
let sphere = SCNSphere(radius: 4)
let sphereNode = SCNNode(geometry: sphere)
sphereNode.position = SCNVector3(x:15, y:0, z:0)
scene.rootNode.addChildNode(sphereNode)
//boxNode.scale = SCNVector3(x: 0.5, y: 0.5, z: 0.5)
Uncomment the last line and you'll see the box (10 x 10 x 10) switch switch from being larger than the sphere (diameter of 8) to being smaller than the sphere. Here it is after the scaling:

Resources