I am currently trying to use a dae file in scenekit for my model as well as my animations. When I try to scale the model, it initially scales correctly. After the animation starts playing, it resets to the original scale. Here is what I am trying to do at the moment:
let sceneView = SCNView()
sceneView.frame = self.view.bounds
sceneView.backgroundColor = UIColor.clearColor()
self.view.addSubview(sceneView)
//Add stuff to scene view
let scene = SCNScene()
sceneView.autoenablesDefaultLighting = true
sceneView.allowsCameraControl = true
sceneView.scene = scene
if let assetScene = SCNScene(named: "halfCircle.dae") {
if let node = assetScene.rootNode.childNodeWithName("pTorus1", recursively: true) {
node.scale = SCNVector3(x: 0.2, y: 0.2, z: 0.2)
scene.rootNode.addChildNode(node)
}
}
Any idea what is going on here?
Thanks.
Related
Is it possible to animate the .backgroundColor property of an SCNView?
Please note, it is easy to animate the background of an actual scene (SCNScene) and I know how to do that. It is also easy to animate the background of a conventional UIView.
I've not been able to figure out how to animate the .backgroundColor property of an SCNView.
Assuming you take the default SceneKit Game Template (the one with the rotating Jet) I got it working by doing this:
Here is my viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene() // SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
// let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
// ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// Configure the initial background color of the SCNView
scnView.backgroundColor = UIColor.red
// Setup a SCNAction that rotates i.Ex the HUE Value of the Background
let animColor = SCNAction.customAction(duration: 10.0) { _ , timeElapsed in
scnView.backgroundColor = UIColor.init(hue: timeElapsed/10, saturation: 1.0, brightness: 1.0, alpha: 1.0)
}
// Run the Action (here using the rootNode)
scene.rootNode.runAction(animColor)
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
}
This might not be the best solution, but using a SCNTransaction I had no luck. Hope I could help in some way.
Just an addendum to the amazing & correct answer of #ZAY.
You have to do a frame-by-frame animation of the color and,
For some reason,
You do the action on the scene view
But. You run the action on the root node of the scene.
So, it's a miracle.
Works perfectly.
I have added a Human 3d Model in Scene Kit. Its background is black how to make it as white as front view? I have used this in swift app, used scene kit and human 3d model, Please check image I have attached..
Back View
3d Model Settings
Code :-
//MARK: - Scene Related Methods
func loadScene() {
self.removeExistingNodes()
loadSceneLayer(fileName: "FinalBaseMesh.obj")
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = false
load3DScene()
//layerSelectionIndex = 0
sceneView.scene = scene
}
func load3DScene() {
sceneView.scene = scene
// Allow user to manipulate camera
sceneView.allowsCameraControl = true
sceneView.backgroundColor = UIColor.white
sceneView.cameraControlConfiguration.allowsTranslation = true
sceneView.cameraControlConfiguration.panSensitivity = 0.9
sceneView.delegate = self as SCNSceneRendererDelegate
// sceneView.isPlaying = true
for reco in sceneView.gestureRecognizers! {
if let panReco = reco as? UIPanGestureRecognizer {
panReco.maximumNumberOfTouches = 1
}
}
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action:#selector(handleTap(_:)))
sceneView.addGestureRecognizer(tapGesture)
self.addSavedNode()
}
func loadSceneLayer(fileName: String) {
scene = SCNScene(named: fileName) ?? SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 6.5, z: 20)
scene.rootNode.addChildNode(cameraNode)
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light?.type = .omni
lightNode.position = SCNVector3(x: 0, y: 6.5, z: 20)
scene.rootNode.addChildNode(lightNode)
// 6: Creating and adding ambien light to scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light?.type = .ambient
ambientLightNode.light?.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
}
I forgot to add last method please check loadstonelayer method
Remove all the camera control code
See How to rotate object in a scene with pan gesture - SceneKit for rotating an object
Put in a light node at a negative z facing the object
I'm trying to modify Xcode's default game setup so that I can: program an animation into the geometry, scrub through that animation, and let the user playback the animation automatically.
I managed to get the scrubbing of the animation to work by setting the view's scene time based on the value of a scrubber. However, when I set the isPlaying boolean on the SCNSceneRenderer to true, it resets the time to 0 on every frame, and I can't get it to move off the first frame.
From the docs, I'm assuming this means it won't detect my animation and thinks the duration of all animations is 0.
Here's my viewDidLoad function in my GameViewController:
override func viewDidLoad() {
super.viewDidLoad()
// create a new scene
let scene = SCNScene(named: "art.scnassets/ship.scn")!
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// define the animation
//ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))
let positionAnimation = CAKeyframeAnimation(keyPath: "position.y")
positionAnimation.values = [0, 2, -2, 0]
positionAnimation.keyTimes = [0, 1, 3, 4]
positionAnimation.duration = 5
positionAnimation.usesSceneTimeBase = true
// retrieve the SCNView
let scnView = self.view as! SCNView
scnView.delegate = self
// add the animation
ship.addAnimation(positionAnimation, forKey: "position.y")
// set the scene to the view
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
// play the scene
scnView.isPlaying = true
//scnView.loops = true
}
Any help is appreciated! :)
References:
sceneTime:
https://developer.apple.com/documentation/scenekit/scnscenerenderer/1522680-scenetime
isPlaying:
https://developer.apple.com/documentation/scenekit/scnscenerenderer/1523401-isplaying
related question:
SceneKit SCNSceneRendererDelegate - renderer function not called
I couldn't get it to work in an elegant way, but I fixed it by adding this Timer call:
Timer.scheduledTimer(timeInterval: timeIncrement, target: self, selector: (#selector(updateTimer)), userInfo: nil, repeats: true)
timeIncrement is a Double set to 0.01, and updateTimer is the following function:
// helper function updateTimer
#objc func updateTimer() {
let scnView = self.view.subviews[0] as! SCNView
scnView.sceneTime += Double(timeIncrement)
}
I'm sure there's a better solution, but this works.
sceneTime is automatically set to 0.0 after actions and animations are run on every frame.
Use can use renderer(_:updateAtTime:) delegate method to set sceneTime to the needed value before SceneKit runs actions and animations.
Make GameViewController comply to SCNSceneRendererDelegate:
class GameViewController: UIViewController, SCNSceneRendererDelegate {
// ...
}
Make sure you keep scnView.delegate = self inside viewDidLoad().
Now implement renderer(_:updateAtTime:) inside your GameViewController class:
// need to remember scene start time in order to calculate current scene time
var sceneStartTime: TimeInterval? = nil
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
// if startTime is nil assign time to it
sceneStartTime = sceneStartTime ?? time
// make scene time equal to the current time
let scnView = self.view as! SCNView
scnView.sceneTime = time - sceneStartTime!
}
I need lights to stay "stationary" in my scene. The best lighting method I've found so far is to actually to use scnView.autoenablesDefaultLighting = true however, I can't figure out if there's any way to control some of the attributes. The intensity of the light is a BIT too bright, the location of the light is a BIT different than where I'd like it to be, those kinds of properties.
I've tried using all sorts of other lights, coding them individually BUT since they add to the scene as nodes, the lights (in those cases) themselves will move when I set scnView.allowsCameraControl = true. The default lighting is the only one that will remain "stationary" once the user begins to move the camera around. Can you access/control the properties of the default lighting?
Forget allowsCameraControl and default cameras and lights if you want control of your scene.
let sceneView = SCNView()
let cameraNode = SCNNode() // the camera
var baseNode = SCNNode() // the basic model-root
let keyLight = SCNLight() ; let keyLightNode = SCNNode()
let ambientLight = SCNLight() ; let ambientLightNode = SCNNode()
func sceneSetup() {
let scene = SCNScene()
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 50)
scene.rootNode.addChildNode(cameraNode)
keyLight.type = SCNLightTypeOmni
keyLightNode.light = keyLight
keyLightNode.position = SCNVector3(x: 10, y: 10, z: 5)
cameraNode.addChildNode(keyLightNode)
ambientLight.type = SCNLightTypeAmbient
let shade: CGFloat = 0.40
ambientLight.color = UIColor(red: shade, green: shade, blue: shade, alpha: 1.0)
ambientLightNode.light = ambientLight
cameraNode.addChildNode(ambientLightNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
}
Move or rotate cameraNode to effect motion in view. Or, move or rotate baseNode. Either way, your light stay fixed relative to the camera.
If you want your lights fixed relative to the model, make them children of baseNode instead of the camera.
If someone who wondering how to setup entire scene with new Scenekit integration with swift ui go though this. This work fine.
struct TestControllerNew: View {
let sceneView = SCNView()
let cameraNode = SCNNode()
var baseNode = SCNNode()
let id = "D69A09F8-EA80-4231-AD35-4A9908B4343C"
var scene = SCNScene()
var body: some View {
SceneView(
scene: sceneSetup(),
pointOfView: cameraNode,
options: [
.autoenablesDefaultLighting
]
)
}
func getTermsOfUseURL() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0].appendingPathComponent("\(id).usdz")
}
func sceneSetup() -> SCNScene {
let scene = try! SCNScene(url: getTermsOfUseURL())
// add to an SCNView
sceneView.scene = scene
// add the container node containing all model elements
sceneView.scene!.rootNode.addChildNode(baseNode)
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 1, 10)
scene.rootNode.addChildNode(cameraNode)
// view the scene through your camera
sceneView.pointOfView = cameraNode
// add gesture recognizers here
return scene
}
}
Here the USDZ file get from document directory as a URL, you can use name instead of that.
I created a floor in a scene and loaded a collada file(.dae) into my scene and tried to create a plane below that model. But I have problems like below.
This problem generates only when custom camera is used. Scene renders properly when system camera is used i.e if no custom camera is added
My code is as below:
import UIKit import SceneKit
class TestVC: UIViewController, SCNSceneRendererDelegate {
var cameraNode: SCNNode!
var modelNode: SCNNode!
var scnView: SCNView!
var camNode: SCNNode!
var cameraPosition: SCNVector3!
override func viewDidLoad() {
super.viewDidLoad()
scnView = SCNView(frame: self.view.frame)
scnView.delegate = self
view.addSubview(scnView)
scnView.delegate = self
let scene = SCNScene()
scnView.scene = scene
//camera
let camera = SCNCamera()
cameraNode = SCNNode()
cameraNode.camera = camera
camera.zFar = 1000
cameraPosition = SCNVector3(0, 100, 150)
cameraNode.position = cameraPosition
scene.rootNode.addChildNode(cameraNode)
//floor
let floor = SCNFloor()
floor.reflectivity = 0
let floorNode = SCNNode(geometry: floor)
let firstmaterial = SCNMaterial()
firstmaterial.diffuse.contents = UIImage(named: "art.scnassets/grass.jpg")
firstmaterial.diffuse.wrapS = SCNWrapMode.Repeat
firstmaterial.diffuse.wrapT = SCNWrapMode.Repeat
floor.materials = [firstmaterial]
scene.rootNode.addChildNode(floorNode)
let light = SCNLight()
let lightNode = SCNNode()
lightNode.light = light
light.type = SCNLightTypeAmbient
light.color = UIColor.darkGrayColor()
scene.rootNode.addChildNode(lightNode)
let light1 = SCNLight()
let light1Node = SCNNode()
light1Node.light = light1
light1Node.position = SCNVector3(0, 200, -100)
light1.type = SCNLightTypeOmni
scene.rootNode.addChildNode(light1Node)
let modelScene = SCNScene(named: "Barcelona Chair.dae")
let solNode = modelScene?.rootNode
solNode?.eulerAngles = SCNVector3(GLKMathDegreesToRadians(-90), 0, 0)
scene.rootNode.addChildNode(solNode!)
scnView.allowsCameraControl = true
let plane = SCNPlane(width: 100, height: 100)
let planeNode = SCNNode(geometry: plane)
planeNode.pivot = SCNMatrix4MakeRotation(Float(M_PI_2), 1, 0, 0)//(CGFloat(M_PI_2), 1, 0, 0)
planeNode.position = SCNVector3(0, 1, 0)
plane.firstMaterial?.diffuse.contents = UIColor.redColor()
scene.rootNode.addChildNode(planeNode)
}
What you're probably seeing is known as "z-fighting". Basically, your floor and your plane are coexisting - they are coplanar - so the rendering of them is ambiguous. Try lifting the plane up a little by setting its position higher than the floor plane, and you should see this problem go away.
Don't know why the above problem exists but I got a different way of doing the thing. You can use SCNShape to draw an overlay over a floor. I drew a rectangular path using UIBezierPath and set the path property of the SCNShape class. :)