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()
}
}
}
Related
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.
I am using the swift charts library found here. When I call our api, we have anywhere from 1 data point up to 24 data points for 24 hrs in a day. When I have a lot of data our width of bars are great. When I have 1 or or up to 5 the widths are very wide and dont look good. I assume its because its trying to fill up the space. does anyone know how I could adjust it to be skinny and not take up the space.
In update view I tried to do various different zooms based on the amount of data but it seems as if there is a min zoom or something in the library.
//code below
import Charts
import SwiftUI
struct TransactionBarChartView: UIViewRepresentable {
let entries: [BarChartDataEntry]
let barChart = BarChartView()
#Binding var selectedYear: Int
#Binding var selectedItem: String
func makeUIView(context: Context) -> BarChartView {
barChart.delegate = context.coordinator
return barChart
}
func updateUIView(_ uiView: BarChartView, context: Context) {
let dataSet = BarChartDataSet(entries: entries)
dataSet.label = "Transactions"
uiView.noDataText = "No Data"
uiView.data = BarChartData(dataSet: dataSet)
uiView.rightAxis.enabled = false
/*if uiView.scaleX == 1.0 {
uiView.zoom(scaleX: 1.5, scaleY: 1, x: 0, y: 0)
}*/
if entries.count < 8 {
uiView.zoom(scaleX: 0.2, scaleY: 1, x: 0, y: 0)
}
if entries.count < 4 {
uiView.zoom(scaleX: 0.2, scaleY: 1, x: 0, y: 0)
}
if entries.count < 2 {
uiView.zoom(scaleX: 0.0005, scaleY: 1, x: 0.05, y: 0)
}
uiView.setScaleEnabled(false)
formatDataSet(dataSet: dataSet)
formatLeftAxis(leftAxis: uiView.leftAxis)
formatXAxis(xAxis: uiView.xAxis)
formatLegend(legend: uiView.legend)
uiView.notifyDataSetChanged()
}
class Coordinator: NSObject, ChartViewDelegate {
let parent:TransactionBarChartView
init(parent: TransactionBarChartView) {
self.parent = parent
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
let month = WineTransaction.months[Int(entry.x)]
let quantity = Int(entry.y)
parent.selectedItem = "\(quantity) sold in \(month)"
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func formatDataSet(dataSet: BarChartDataSet) {
dataSet.colors = [.red]
dataSet.valueColors = [.red]
let formatter = NumberFormatter()
formatter.numberStyle = .none
dataSet.valueFormatter = DefaultValueFormatter(formatter: formatter)
}
func formatLeftAxis(leftAxis: YAxis) {
leftAxis.labelTextColor = .red
let formatter = NumberFormatter()
formatter.numberStyle = .none
leftAxis.valueFormatter = DefaultAxisValueFormatter(formatter: formatter)
leftAxis.axisMinimum = 0
}
func formatXAxis(xAxis: XAxis) {
xAxis.valueFormatter = IndexAxisValueFormatter(values: WineTransaction.months)
xAxis.labelPosition = .bottom
xAxis.granularityEnabled = true
xAxis.labelTextColor = .red
}
func formatLegend(legend: Legend) {
legend.textColor = .red
legend.horizontalAlignment = .right
legend.verticalAlignment = .top
legend.drawInside = true
legend.yOffset = 30.0
}
}
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)")
// }
}
}
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)
}
}
I'm working to implement a Zig Zag game and following a set of instructions from an on-line course. I have a swift UIViewController, and I am trying now to use also SCNSceneRenderer (to do the game logic of keeping the game character on the zig-zag path). The code looks like this:
import UIKit
import QuartzCore
import SceneKit
class GameViewController: UIViewController, SCNSceneRenderer{
let scene = SCNScene()
let cameraNode = SCNNode()
var person = SCNNode()
let firstBox = SCNNode()
var goingLeft = Bool()
var tempBox = SCNNode()
var boxNumber = Int()
var prevBoxNumber = Int()
override func viewDidLoad() {
print("Yes -- view did load")
self.createScene()
}
func renderer(render: SCNSceneRenderer, updateAtTime time: TimeInterval){
let deleteBox = self.scene.rootNode.childNode(withName: "\(prevBoxNumber)", recursively: true )
if (deleteBox?.position.x)! > (person.position.x + 1) || (deleteBox?.position.z)! > (person.position.z + 1) {
prevBoxNumber += 1
deleteBox?.removeFromParentNode()
createBox()
}
}
func createBox(){
tempBox = SCNNode(geometry: firstBox.geometry)
let prevBox = scene.rootNode.childNode(withName: "\(boxNumber)", recursively: true )
boxNumber += 1
tempBox.name = "\(boxNumber)"
let randomNumber = arc4random() % 2
switch randomNumber {
case 0:
tempBox.position = SCNVector3Make((prevBox?.position.x)! - firstBox.scale.x, (prevBox?.position.y)!, (prevBox?.position.z)!)
break
case 1:
tempBox.position = SCNVector3Make((prevBox?.position.x)! , (prevBox?.position.y)!, (prevBox?.position.z)! - firstBox.scale.z)
break
default:
break
}
self.scene.rootNode.addChildNode(tempBox)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
if goingLeft == false {
person.removeAllActions()
person.runAction(SCNAction.repeatForever(SCNAction.move(by: SCNVector3Make(-100, 0, 0), duration: 20)))
goingLeft = true
} else {
person.removeAllActions()
person.runAction(SCNAction.repeatForever(SCNAction.move(by: SCNVector3Make(0, 0, -100), duration: 20)))
goingLeft = false
}
print("boxNumber is \(boxNumber)")
}
func createScene(){
boxNumber = 0
prevBoxNumber = 0
self.view.backgroundColor = UIColor.orange
let sceneView = self.view as! SCNView
sceneView.delegate = self
sceneView.scene = scene
//Create Person
let personGeo = SCNSphere(radius: 0.2)
person = SCNNode (geometry : personGeo)
let personMat = SCNMaterial()
personMat.diffuse.contents = UIColor.red
personGeo.materials = [personMat]
person.position = SCNVector3Make(0, 1.1, 0)
scene.rootNode.addChildNode(person)
//Create Camera
cameraNode.camera = SCNCamera()
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = 3
cameraNode.position = SCNVector3Make(20,20,20)
cameraNode.eulerAngles = SCNVector3Make(-45,45,0)
//let constraint = SCNLookAtConstraint(target: firstBox)
let constraint = SCNLookAtConstraint(target: person)
constraint.isGimbalLockEnabled = true
self.cameraNode.constraints = [constraint]
scene.rootNode.addChildNode(cameraNode)
person.addChildNode(cameraNode)
//Create Box
let firstBoxGeo = SCNBox(width: 1.0, height: 1.5, length: 1.0, chamferRadius: 0)
firstBox.geometry = firstBoxGeo
let boxMaterial = SCNMaterial()
boxMaterial.diffuse.contents = UIColor(red: 0.2, green: 0.8, blue: 0.9, alpha: 1.0)
firstBoxGeo.materials = [boxMaterial]
firstBox.position = SCNVector3Make(0,0,0)
scene.rootNode.addChildNode(firstBox)
firstBox.name = "\(boxNumber)"
for _ in 0...6{
createBox()
}
//Create light
let light = SCNNode()
light.light = SCNLight()
light.light?.type = SCNLight.LightType.directional
light.eulerAngles = SCNVector3Make(-45, 45, 0)
scene.rootNode.addChildNode(light)
//Create light2
let light2 = SCNNode()
light2.light = SCNLight()
light2.light?.type = SCNLight.LightType.directional
light2.eulerAngles = SCNVector3Make(45, 45, 0)
scene.rootNode.addChildNode(light2)
}
}
When I added SCNSceneRenderer I get the following error:
"Type 'GameViewController' cannot conform to protocol 'SCNSceneRenderer' because it has requirements that cannot be satisfied"
Since my GameViewController isn't recognized as a SCNSceneRenderer, I also get an error at this line:
sceneView.delegate = self
the error is "Cannot assign value of type 'GameViewController' to type 'SCNSceneRendererDelegate'
I'm new to swift programming, but this seems like I am trying to implement an interface like in Java, I've been looking at the Swift documentation but don't see what I need to do to make my class be functional as an SCNSceneRenderer. I would appreciate help to solve this problem. Thanks!