How to zoom in to a node of a scene that's being displayed in swift ui - swift

I have a Scenekit scene in swift UI, I made the scene a UIViewRepresentable, how can I zoom in on one of the nodes of the scene when the user touches that specific node in the Scenekit scene?
import SceneKit
struct HouseView : UIViewRepresentable {
func makeUIView(context: Context) -> SCNView {
return SCNView(frame: .zero)
}
func updateUIView(_ scnView: SCNView, context: Context) {
let HouseScene = SCNScene(named: "House.scn")
scnView.scene = HouseScene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = false
scnView.backgroundColor = UIColor.systemBackground
scnView.defaultCameraController.maximumVerticalAngle = 10
scnView.defaultCameraController.minimumVerticalAngle = -10
scnView.defaultCameraController.maximumHorizontalAngle = 180
scnView.defaultCameraController.minimumHorizontalAngle = -10
scnView.isJitteringEnabled = true
let CameraNode = HouseScene?.rootNode.childNode(withName: "CameraNode", recursively: true)
CameraNode?.position = SCNVector3(x: 12, y:2, z: 0)
}
}
struct HouseView_Previews: PreviewProvider {
static var previews: some View {
HouseView()
}
}`''

Please see this post: 54058938
Create a basic camera class and you can focus on the node touched and set the distance away from it.

Related

How to make SceneView's background clear in SwiftUI?

I am showing a 3D object in SwiftUI, and I have a problem with making the object's background clear. I have searched and did not find any solution. Is there any solution for this?
private var scene: SCNScene? {
SCNScene(named: "Globe.scnassets/sphere.scn")
}
private var cameraNode: SCNNode? {
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
return cameraNode
}
var body: some View {
SceneView(scene: scene,
pointOfView: cameraNode,
options: [.allowsCameraControl, .autoenablesDefaultLighting])
}
There are, at least, 3 ways to programmatically change a BG in SwiftUI's SceneView.
Default white background
Changing SCNScene's background color
import SwiftUI
import SceneKit
struct ContentView: View {
var scene = SCNScene(named: "model.usdz")
var options: SceneView.Options = [.autoenablesDefaultLighting,
.allowsCameraControl ]
var body: some View {
ZStack {
SceneView(scene: scene, options: options)
.ignoresSafeArea()
let _ = scene?.background.contents = UIColor.black
}
}
}
Using textured double-sided SCNGeometry
struct ContentView: View {
var scene = SCNScene(named: "model.usdz")
var options: SceneView.Options = [.autoenablesDefaultLighting,
.allowsCameraControl ]
let node = SCNNode(geometry: SCNSphere(radius: 500.0))
let img = UIImage(named: "image.jpg")
var body: some View {
ZStack {
let _ = node.geometry?.firstMaterial?.diffuse.contents = img
let _ = node.geometry?.firstMaterial?.isDoubleSided = true
let _ = scene?.rootNode.addChildNode(node)
SceneView(scene: scene, options: options)
.ignoresSafeArea()
}
}
}
Using Procedural Sky Box Texture
You can use MDLSkyCubeTexture as background and lightingEnvironment.
I have found a solution via SceneKit editor, if you want to show an image as background, you can manually do it by going to:
Scene Inspector -> Background and lighting -> Background
and set the image as background:

Same color rendered differently in SpriteView vs. SceneView

For performance reasons I have to switch from SceneView to SpriteView in my macOS project (showing more than 63 scenes did not work with SceneView, but it does with SpriteView).
But now im facing an issue that SpriteView is rendering colors differently than SceneView. Below is a simple reproduction of the issue I am facing.
I have tried a multitude of material and lighting options, but I seem to miss something more fundamental. Help is very much appreciated.
var body: some View {
HStack {
// SpriteView
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.backgroundColor = .white
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SK3DNode()
node.scnScene = self.sphereScene
scene.addChild(node)
return scene
}())
// SceneView
SceneView(scene: sphereScene,
options: [.autoenablesDefaultLighting])
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 5)
let ballNode = SCNNode(geometry: ballGeometry)
let material = SCNMaterial()
material.diffuse.contents = NSColor.purple
material.lightingModel = .physicallyBased
ballGeometry.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
You're absolutely right: SpriteKit processes SceneKit's scenes differently than SceneKit. It's visually noticeable that the lighting intensity, blurring of highlights and decolorization of edges with 90 degree reflection are different. The main tool that can be advised in this case is the use of Ambient Light to additionally illuminate the SpriteKit scene based on the SceneKit content. You should turn a default lighting off (in order to get rid of colorization artifacts) and use regular lights. Here I used directional light.
SpriteView:
import SwiftUI
import SceneKit
import SpriteKit
struct SpriteView: NSViewRepresentable {
var scene = SKScene()
func makeNSView(context: Context) -> SKView {
let skView = SKView(frame: .zero)
skView.presentScene(scene)
scene.backgroundColor = .black
return skView
}
func updateNSView(_ uiView: SKView, context: Context) { }
}
ContentView:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let ambient = SCNNode()
ambient.light = SCNLight()
ambient.light?.type = .ambient
ambient.light?.intensity = 1200
let node = SK3DNode()
node.autoenablesDefaultLighting = false
node.scnScene = self.sphereScene
node.scnScene?.rootNode.addChildNode(ambient)
scene.addChild(node)
return scene
}() )
SceneView(scene: sphereScene, options: [])
}
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
scnScene.background.contents = NSColor.black
let ballNode = SCNNode(geometry: SCNSphere(radius: 5.0))
let directional = SCNNode()
directional.light = SCNLight()
directional.light?.type = .directional
directional.light?.intensity = 500
scnScene.rootNode.addChildNode(directional)
let material = SCNMaterial()
material.lightingModel = .physicallyBased
material.diffuse.contents = NSColor.purple
ballNode.geometry?.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
}
The following worked for me, correcting saturation and brightness brought me near to the SceneKit defaultLighting appearance:
// get object and manipulate
let object = scene.rootNode.childNode(withName: "object", recursively: false)
let color = NSColor(named: "\(colorNr)")?
.usingColorSpace(.displayP3) // specify color space, important!
object?.geometry?.firstMaterial?.lightingModel = .physicallyBased
// correct color for SpriteView
let color2 = NSColor(hue: color?.hueComponent ?? 0,
saturation: (color?.saturationComponent ?? 0) * 0.55,
brightness: (color?.brightnessComponent ?? 0) * 0.55 + 0.45,
alpha: 1.0)
object?.geometry?.firstMaterial?.diffuse.contents = color2
object?.geometry?.firstMaterial?.diffuse.intensity = 0.9
object?.geometry?.firstMaterial?.roughness.contents = 0.9

ARKit – Environment Occlusion

In Unity we can implement occlusion with Environment Depth. Which uses ARKit behind the scene. How can I achieve same behaviour in iOS ARkit.
I know we can configure frame semantics with depth, but I doubt it is really same as unity environment depth occlusion?
// Build the set of required frame semantics.
let semantics: ARConfiguration.FrameSemantics = [.sceneDepth]
configuration.frameSemantics = semantics
session.run(configuration)
In ARKit implement sceneReconstruction option, and in RealityKit turn on .occlusion.
The only drawback is an ugly mask with soft dilated edges around real-world objects...
import RealityKit
import SwiftUI
import ARKit
struct ContentView: View {
var body: some View {
return ARContainer().ignoresSafeArea()
}
}
struct ARContainer: UIViewRepresentable {
func makeUIView(context: Context) -> ARView {
let arView = ARView(frame: .zero)
arView.cameraMode = .ar
arView.automaticallyConfigureSession = false
let config = ARWorldTrackingConfiguration()
config.sceneReconstruction = .mesh
arView.session.run(config)
arView.environment.sceneUnderstanding.options = .occlusion
let box: MeshResource = .generateBox(size: 0.5)
let material = SimpleMaterial(color: .green, isMetallic: true)
let entity = ModelEntity(mesh: box, materials: [material])
let anchor = AnchorEntity(world: [0,0,-1.5])
anchor.addChild(entity)
arView.scene.anchors.append(anchor)
return arView
}
func updateUIView(_ uiView: ARView, context: Context) { }
}

How to add 3D shapes in Swift UI?

I want to know how to add 3D shapes (eg. Sphere) in swift UI
I have tried adding a scnscene and using that in swift UI but I get a error message
//SceneKit
class myscene: SCNScene{
override init(){
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder: ) has not been implemented")
}
}
//Swift UI
struct ContentView: View {
var body: some View {
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
}
}
The error message is on var body: some View { line and reads as follows :
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Please help me with this problem......
Here is simplest code to demo how to setup SceneKit scene with your sphere. Hope it helps.
import SwiftUI
import SceneKit
struct SceneKitView: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.black
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
sceneView.scene?.rootNode.addChildNode(spherenode)
return sceneView
}
func updateUIView(_ uiView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
}
typealias UIViewType = SCNView
}
struct DemoSceneKit: View {
var body: some View {
SceneKitView()
}
}
struct DemoSceneKit_Previews: PreviewProvider {
static var previews: some View {
DemoSceneKit()
}
}

2d overlay elements not rendering over Scene Kit scene

I'm using an SKScene as an overlay over my Scene Kit scene, but when I add a child node to the SKScene it doesn't render to the screen. Here is my code(truncated):
import SceneKit
import SpriteKit
class GameViewController {
var scene = SCNScene()
var overlay = SKScene()
var expand = SKSpriteNode(imageNamed: "expand")
var trash = SKSpriteNode(imageNamed: "trash")
var palette = SKSpriteNode(imageNamed: "palette")
override func viewDidLoad() {
super.viewDidLoad()
// set up scene
let sceneView = view as! SCNView
sceneView.backgroundColor = SKColor.blackColor()
sceneView.autoenablesDefaultLighting = true
sceneView.showsStatistics = false
setupScene()
sceneView.scene = scene
sceneView.pointOfView = cameraNode
sceneView.overlaySKScene = overlay
setupEnvironment()
setupHud()
}
func setupHud() {
var sizeOfModifiers = (expand.frame.size.width + trash.frame.size.width + palette.frame.size.width) / 2
trash.anchorPoint = CGPointMake(0, 0)
palette.anchorPoint = CGPointMake(0, 0)
expand.anchorPoint = CGPointMake(0, 0)
trash.position = CGPointMake((view.frame.width / 2) - sizeOfModifiers, 0)
palette.position = CGPointMake(trash.position.x + trash.frame.size.width, 0)
expand.position = CGPointMake(palette.position.x + trash.frame.size.width, 0)
overlay.addChild(trash)
overlay.addChild(palette)
overlay.addChild(expand)
}
}
From what I see here, the code is written correctly. The images are loading, but it doesn't get rendered over the 3d elements in the scene.
I asked this question prematurely. From my research, the SKScene needs to be a separate class. I made a schoolboy error.