SceneKit – SwiftUI implementation doesn't seem to update anything - swift

Swift 5.x iOS 15
Trying to understand SceneKit, but I am stuck. I created a scene and display it with SwiftUI. But when I try and run a function with that class the scene is defined it does nothing. When I tap on the label "push" I want the ball to jump in the air. Nothing happens.
import SwiftUI
import SceneKit
struct ContentView: View {
var body: some View {
let scene = GameScene()
Text("push")
.onTapGesture {
let foo = SharedView.shared
print("push ",foo.xCord,foo.yCord,foo.zCord)
scene.printMe()
}
}
}
class SharedView: ObservableObject {
#Published var xCord:Float = 0
#Published var yCord:Float = 0
#Published var zCord:Float = 0
#Published var xCord2:Float = 0
#Published var yCord2:Float = 1.8
#Published var zCord2:Float = 0
static var shared = SharedView()
}
...
class SceneDelegate: NSObject, SCNSceneRendererDelegate {
let foo = SharedView.shared
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
print("running!!")
}
static var shared = SceneDelegate()
}
struct GameScene: View {
// #ObservedObject var foo = SharedView.shared
#StateObject var foo = SharedView.shared
#State var scene = SCNScene(named: "MyScene.scn")
#State var cameraNode = SCNNode()
#State var ball = SCNNode()
#State var box = SCNNode()
var body: some View {
scene?.isPaused = false
self.scene?.rootNode.enumerateChildNodes({ (node, _ ) in
if node.name == "ball" {
Task {
ball = node
ball.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: ball, options: [:]))
ball.physicsBody?.isAffectedByGravity = true
ball.physicsBody?.restitution = 1.2
}
}
if node.name == "box" {
Task {
box = node
box.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: box, options: [:]))
box.physicsBody?.isAffectedByGravity = false
box.physicsBody?.restitution = 1
}
}
})
cameraNode.camera = SCNCamera()
cameraNode.position.x = foo.xCord
cameraNode.position.y = foo.yCord
cameraNode.worldPosition.z = foo.zCord
cameraNode.eulerAngles = SCNVector3(foo.xCord2,foo.yCord2,foo.zCord2)
cameraNode.camera = SCNCamera()
let sceneView = SceneView(
scene: scene,
pointOfView: cameraNode,
options: [.autoenablesDefaultLighting,.allowsCameraControl], delegate: SceneDelegate.shared)
let dummyNode = scene!.rootNode.childNode(withName: "DummyNode", recursively: false)
dummyNode?.position = SCNVector3(0, 0, 0)
return sceneView
.frame(width: 1024, height: 256, alignment: .center)
}
func printMe() {
print(cameraNode.eulerAngles, cameraNode.position)
ball.physicsBody?.applyForce(SCNVector3(0,10,0), asImpulse: true)
}
}

Related

SCNLookAtConstraint && SCNDistanceConstraint

Running on iOS16
Trying to get a better handle on SCNLookATConstraints, but struggling to get them to work in this demo app.
When I tap on the scene the box moves, but the camera doesn't follow it.
import SwiftUI
import SceneKit
import Combine
let screenSize: CGRect = UIScreen.main.bounds
let screenWidth: CGFloat = UIScreen.main.bounds.width
let screenHeight: CGFloat = UIScreen.main.bounds.height
let push = PassthroughSubject<Float,Never>()
var pusher: AnyCancellable!
struct CoreView: View {
#State var scene = SCNScene()
#State var place:Float = 0.0
var body: some View {
CustomSceneView(scene: scene, options: [])
.frame(width: screenWidth, height: screenHeight)
.onTapGesture {
place += 0.1
push.send(place)
}
}
}
struct ContentView: View {
var body: some View {
CoreView()
}
}
struct CustomSceneView: UIViewRepresentable {
var scene: SCNScene
var options: [Any]
var delegate: SCNRenderer!
var view = SCNView()
func makeUIView(context: Context) -> SCNView {
view.scene = scene
view.allowsCameraControl = true
view.autoenablesDefaultLighting = false
let cubeGeometry = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0)
cubeGeometry.firstMaterial?.diffuse.contents = UIColor.red
let cubeNode = SCNNode(geometry: cubeGeometry)
cubeNode.name = "cubeNode"
cubeNode.simdPosition = SIMD3(0,0,0)
let camera = SCNCamera()
camera.fieldOfView = 60.0
camera.focalLength = 100.0
camera.sensorHeight = 0.0
camera.wantsDepthOfField = true
camera.focusDistance = 0.8
camera.fStop = 5.6
camera.wantsHDR = false
camera.apertureBladeCount = 4
camera.motionBlurIntensity = 1.0
camera.name = "camera"
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.name = "cameraNode"
let lookAtConstraint = SCNLookAtConstraint(target: cubeNode)
lookAtConstraint.influenceFactor = 1.0
lookAtConstraint.isGimbalLockEnabled = true
view.delegate = context.coordinator
scene.rootNode.addChildNode(cameraNode)
view.pointOfView = scene.rootNode.childNode(withName: "cameraNode", recursively: true)
view.pointOfView?.simdPosition = SIMD3<Float>(0, 0.0, 2)
scene.rootNode.addChildNode(cubeNode)
view.defaultCameraController.interactionMode = .fly
view.defaultCameraController.inertiaEnabled = true
//view.defaultCameraController.maximumVerticalAngle = 45
view.allowsCameraControl = true
pusher = push.debounce(for: .seconds(0.5), scheduler: RunLoop.main)
.throttle(for: 0.01, scheduler: RunLoop.main, latest: true)
.sink(receiveValue: { [self] distance in
cubeNode.simdPosition = SIMD3(distance,0,0)
})
return view
}
func updateUIView(_ view: SCNView, context: Context) {
print("update")
}
func makeCoordinator() -> Coordinator {
Coordinator(view)
}
}
class Coordinator: NSObject, SCNSceneRendererDelegate {
private let view: SCNView
private var cubeNode: SCNNode!
private var cameraNode: SCNNode!
init(_ view: SCNView) {
self.view = view
super.init()
}
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
cameraNode = renderer.scene?.rootNode.childNode(withName: "cameraNode", recursively: true)
cubeNode = renderer.scene?.rootNode.childNode(withName: "cubeNode", recursively: true)
print("cubeNode \(cubeNode)")
print("cameraNode \(cameraNode)")
if cubeNode != nil && cameraNode != nil {
let distanceConstraint = SCNDistanceConstraint(target: cubeNode)
distanceConstraint.minimumDistance = 1
distanceConstraint.maximumDistance = 2
let lookAtConstraint = SCNLookAtConstraint(target: cubeNode)
lookAtConstraint.influenceFactor = 1.0
lookAtConstraint.isGimbalLockEnabled = true
cameraNode.constraints = [lookAtConstraint, distanceConstraint]
}
}
}
Tried adding the camera constraints to the built, the coordinator and shown here the renderer. The code is seeing it, but ignoring it.
Tried moving the scene with my mouse and clicking on it to move it, neither work.

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:

Converting worldPosition into a SceneView position with SceneKit/Swift

iOS 15, Swift 5
I am trying to get sceneView [SwiftUI SceneKit plugin] to work with adding/finding nodes within my scene. And I feel I am almost there and yet I am not.
Sadly the hits function here finds nothing, which is why I ended up commenting it out. I tried used projectPoint after that, but the results make no sense?
Now I did get this working using UIViewRespresentable, but that solution doesn't use sceneView at all.
I want to convert the drag coordinates to the SCNView coordinates since since I think I can find/match objects.
import SwiftUI
import SceneKit
struct ContentView: View {
let settings = SharedView.shared
var (scene,cameraNode) = GameScene.shared.makeView()
var body: some View {
GameView(scene: scene, cameraNode: cameraNode)
}
}
struct GameView: View {
let settings = SharedView.shared
#State var scene: SCNScene
#State var cameraNode: SCNNode
var body: some View {
SceneView(
scene: scene,
pointOfView: cameraNode,
options: [.autoenablesDefaultLighting, .rendersContinuously ], delegate: SceneDelegate())
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ gesture in
settings.location = gesture.location
}).sequenced(before: TapGesture().onEnded({ _ in
GameScene.shared.tapV()
}))
)
}
}
class SharedView: ObservableObject {
#Published var location:CGPoint!
#Published var view:UIView!
#Published var scene:SCNScene!
#Published var sphereNode:SCNNode!
static var shared = SharedView()
}
#MainActor class SceneDelegate: NSObject, SCNSceneRendererDelegate {
}
class GameScene: UIView {
static var shared = GameScene()
let settings = SharedView.shared
var view: SCNView!
var scene: SCNScene!
func makeView() -> (SCNScene,SCNNode) {
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
let sphere = SCNSphere(radius: 1.0)
sphere.materials = [material]
let sphereNode = SCNNode()
sphereNode.geometry = sphere
sphere.name = "sphereNode"
let camera = SCNCamera()
camera.fieldOfView = 90.0
let light = SCNLight()
light.color = UIColor.white
light.type = .omni
let cameraNode = SCNNode()
cameraNode.simdPosition = SIMD3<Float>(0.0, 0.0, 6)
cameraNode.camera = camera
cameraNode.light = light
scene = SCNScene()
scene.background.contents = UIColor.black
scene.rootNode.addChildNode(sphereNode)
scene.rootNode.addChildNode(cameraNode)
view = SCNView()
view.scene = scene
view.pointOfView = cameraNode
settings.sphereNode = sphereNode
return (scene, cameraNode)
}
func tapV() {
let location = settings.location
let hits = view.hitTest(location!, options: [.boundingBoxOnly: true, .firstFoundOnly: true, .searchMode: true])
print("hits \(hits.count)")
// for hit in hits {
let material = SCNMaterial()
material.diffuse.contents = UIColor.yellow
let geometry = SCNSphere(radius: 0.1)
geometry.materials = [material]
let node = SCNNode()
node.geometry = geometry
// node.simdPosition = hit.simdWorldCoordinates
let projectedOrigin = view.projectPoint(SCNVector3Zero)
var worldPoint = view.unprojectPoint(SCNVector3(location!.x, location!.y, CGFloat(0)))
node.worldPosition = worldPoint
view.scene!.rootNode.addChildNode(node)
print("node \(node.position) \(hits.count)")
// }
}
}

Adding multiple SCNNode(s) at the same time

I have this function named addShapes. I want it to create 3 shapes
import ARKit
import SwiftUI
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
let config = ARWorldTrackingConfiguration()
#Binding var addBox: Int
#Binding var reset: Bool
#Binding var node: SCNNode
fileprivate func addShapes() {
let letBall1Geo = SCNSphere(radius: 0.05)
letBall1Geo.firstMaterial?.diffuse.contents = UIColor.red
letBall1Geo.firstMaterial?.specular.contents = UIColor.white
let ball1 = SCNNode(geometry: letBall1Geo)
ball1.position = SCNVector3(0, 0, 0)
self.arView.scene.rootNode.addChildNode(ball1)
let letBall2Geo = SCNSphere(radius: 0.05)
letBall2Geo.firstMaterial?.diffuse.contents = UIColor.white
letBall2Geo.firstMaterial?.specular.contents = UIColor.white
let ball2 = SCNNode(geometry: letBall2Geo)
ball1.position = SCNVector3(1, 1, 1)
self.arView.scene.rootNode.addChildNode(ball2)
let stickGeo = SCNCapsule(capRadius: 0.05, height: 0.1)
stickGeo.firstMaterial?.diffuse.contents = UIColor.blue
stickGeo.firstMaterial?.specular.contents = UIColor.white
let stick = SCNNode(geometry: stickGeo)
stick.position = SCNVector3(2, 2, 2)
self.arView.scene.rootNode.addChildNode(stick)
}
fileprivate func removeCube() {
/////
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.autoenablesDefaultLighting = true
arView.debugOptions = [ARSCNDebugOptions.showFeaturePoints, ARSCNDebugOptions.showWorldOrigin]
arView.session.run(self.config)
return arView
}
func updateUIView(_ uiView: ARSCNView,
context: Context) {
if addBox > 0 {
self.addShapes()
}
if reset {
self.removeCube()
}
}
}
The problem is that it's only adding the first shape (ball1) and not all of them. Do you know why?
Thanks in advance!
Your code works fine (a position was a problem):
import SwiftUI
import ARKit
struct ContentView : View {
#State var addBox: Int = 3
#State var reset: Bool = false
#State var node: SCNNode = SCNNode()
var body: some View {
return SceneKitView(addBox: $addBox,
reset: $reset,
node: $node).edgesIgnoringSafeArea(.all)
}
}
...
struct SceneKitView: UIViewRepresentable {
let arView = ARSCNView(frame: .zero)
let config = ARWorldTrackingConfiguration()
#Binding var addBox: Int
#Binding var reset: Bool
#Binding var node: SCNNode
fileprivate func addShapes() {
let letBall1Geo = SCNSphere(radius: 0.05)
letBall1Geo.firstMaterial?.diffuse.contents = UIColor.red
letBall1Geo.firstMaterial?.specular.contents = UIColor.white
let ball1 = SCNNode(geometry: letBall1Geo)
ball1.position = SCNVector3(0, 0, -1)
self.arView.scene.rootNode.addChildNode(ball1)
let letBall2Geo = SCNSphere(radius: 0.05)
letBall2Geo.firstMaterial?.diffuse.contents = UIColor.green
letBall2Geo.firstMaterial?.specular.contents = UIColor.white
let ball2 = SCNNode(geometry: letBall2Geo)
ball1.position = SCNVector3(0, 0.2, -1)
self.arView.scene.rootNode.addChildNode(ball2)
let stickGeo = SCNCapsule(capRadius: 0.05, height: 0.1)
stickGeo.firstMaterial?.diffuse.contents = UIColor.blue
stickGeo.firstMaterial?.specular.contents = UIColor.white
let stick = SCNNode(geometry: stickGeo)
stick.position = SCNVector3(0, 0.4, -1)
self.arView.scene.rootNode.addChildNode(stick)
}
func makeUIView(context: Context) -> ARSCNView {
arView.scene = SCNScene()
arView.autoenablesDefaultLighting = true
arView.debugOptions = [.showFeaturePoints, .showWorldOrigin]
arView.session.run(self.config)
return arView
}
func updateUIView(_ uiView: ARSCNView, context: Context) {
if addBox > 0 {
self.addShapes()
}
}
}

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()
}
}