Can you change the properties of scnView.autoenablesDefaultLighting?
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.
Swift 4 - SpriteKit over SceneKit - prevent touch for underlying camera controls
Turn off allowsCameraControl and implement your own camera controls instead. The allowsCameraControl is handy for testing/debugging purposes and cannot be disabled partly such as for the doubletap behavior only. Which besides conflicts with other gestures gets annoying as a double tap is easily made by accident and resets the screen.
autoenablesDefaultLighting is too bright in iOS 12 and SCNView.pointOfView is not effective
You can try enabling HDR. It should result in a balanced exposure
scnView?.pointOfView?.camera?.wantsHDR = true
With HDR enabled, you can even control exposure compensation with
scnView?.pointOfView?.camera?.exposureOffset
swift/scenekit problems getting touch events from SCNScene and overlaySKScene
This is "lifted" straight out of Xcode's Game template......
Add a gesture recognizer in your viewDidLoad:
// add a tap gesture recognizer
let tapGesture = UITapGestureRecognizer(target: self, action:
#selector(handleTap(_:)))
scnView.addGestureRecognizer(tapGesture)
func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let scnView = self.view as! SCNView
// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = scnView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result: AnyObject = hitResults[0]
// result.node is the node that the user tapped on
// perform any actions you want on it
}
}
SwiftUI with SceneKit: How to use button action from view to manipulate underlying scene
I found a solution using @EnvironmentalObject
but I am not completely sure, if this is the right approach. So comments on this are appreciated.
First, I moved the SCNScene into it’s own class and made it an OberservableObject
:
class Scene: ObservableObject {
@Published var scene: SCNScene
init(_ scene: SCNScene = SCNScene()) {
self.scene = scene
self.scene = setup(scene: self.scene)
}
// code omitted which deals with setting the scene up and adding/removing the box
// method used to determine if the box node is present in the scene -> used later on
func boxIsPresent() -> Bool {
return scene.rootNode.childNode(withName: "box", recursively: true) != nil
}
}
I inject this Scene
into the app as an .environmentalObject()
, so it is available to all views:
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Create the SwiftUI view that provides the window contents.
let sceneKit = Scene()
let mainView = MainView().environmentObject(sceneKit)
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: mainView)
self.window = window
window.makeKeyAndVisible()
}
}
}
MainView
is slightly altered to call SceneView
(a UIViewRepresentable
) with the separate Scene
for the environment:
struct MainView: View {
@EnvironmentObject var scene: Scene
var body: some View {
ZStack {
SceneView(scene: self.scene.scene)
HUDView()
}
}
}
Then I make the Scene
available to the HUDView
as an @EnvironmentalObject
, so I can reference the scene and its methods and call them from the Button action. Another effect is, I can query the Scene
helper method to determine, if a Button should be active or not:
struct HUDView: View {
@EnvironmentObject var scene: Scene
@State private var canAddBox: Bool = false
@State private var canRemoveBox: Bool = true
var body: some View {
VStack {
HStack(alignment: .center, spacing: 0) {
Spacer ()
ButtonView(
action: {
self.scene.addBox()
if self.scene.boxIsPresent() {
self.canAddBox = false
self.canRemoveBox = true
}
},
icon: "plus.square.fill",
isActive: $canAddBox
)
ButtonView(
action: {
self.scene.removeBox()
if !self.scene.boxIsPresent() {
self.canRemoveBox = false
self.canAddBox = true
}
},
icon: "minus.square.fill",
isActive: $canRemoveBox
)
}
.background(Color.white.opacity(0.2))
Spacer()
}
}
}
Here is the ButtonView code, which used a @Binding
to set its active state (not sure about the correct order for this with the@State property in
HUDView`):
struct ButtonView: View {
let action: () -> Void
var icon: String = "square"
@Binding var isActive: Bool
var body: some View {
Button(action: action) {
Image(systemName: icon)
.font(.title)
.accentColor(self.isActive ? Color.white : Color.white.opacity(0.5))
}
.frame(width: 44, height: 44)
.disabled(self.isActive ? false: true)
}
}
Anyway, the code works now. Any thoughts on this?
Display stretched material apply on SCNGeometrySource node
Texture's scale issue:
I don't know the size of your model but I think the problem is – you increased a scale although you have to decrease it to properly map a texture.
My texture's size is 2K square (2048x2048, 72dpi).
Here's a code (I used macOS version here):
internal func getFaceDrawnFrom(pos1: SCNVector3,
pos2: SCNVector3,
pos3: SCNVector3) -> SCNNode {
let triGeo = generateGeoFrom(vector1: pos1,
vector2: pos2,
vector3: pos3)
let material = SCNMaterial()
material.diffuse.contents = NSImage.Name("art.scnassets/jaguar.jpg")
// your scale
// material.diffuse.contentsTransform = SCNMatrix4MakeScale(32, 32, 0)
material.isDoubleSided = true
material.diffuse.contentsTransform = .init(
m11: 0.05, m12: 0, m13: 0, m14: 0,
m21: 0, m22: 0.05, m23: 0, m24: 0,
m31: 0, m32: 0, m33: 1, m34: 0,
m41: 0, m42: 0, m43: 0, m44: 1)
material.diffuse.wrapS = .repeat
material.diffuse.wrapT = .repeat
square.materials = [material]
let node = SCNNode(geometry: triGeo)
node.name = "triangularFace"
scene.rootNode.addChildNode(node)
return node
}
Then:
fileprivate func generateGeoFrom(vector1: SCNVector3,
vector2: SCNVector3,
vector3: SCNVector3) -> SCNGeometry {
let normalsPerFace = 3
let indices: [Int32] = [0, 1, 2]
let source = SCNGeometrySource(vertices: [vector1, vector2, vector3])
let vecs = [vector1, vector2, vector3].map {
[SCNVector3](repeating: $0, count: normalsPerFace)
}.flatMap { $0 }
let normals: [SCNVector3] = vecs
let normalSource = SCNGeometrySource(normals: normals)
let cgp1 = CGPoint(x: CGFloat(vector1.x), y: CGFloat(vector1.y))
let cgp2 = CGPoint(x: CGFloat(vector2.x), y: CGFloat(vector2.y))
let cgp3 = CGPoint(x: CGFloat(vector3.x), y: CGFloat(vector3.y))
let textcoord = SCNGeometrySource(textureCoordinates: [cgp1, cgp2, cgp3])
let element = SCNGeometryElement(indices: indices,
primitiveType: .triangles)
return SCNGeometry(sources: [source, normalSource, textcoord],
elements: [element])
}
Then let's call the method.
let instance = getFaceDrawnFrom(pos1: SCNVector3(x: 0, y: 0, z: 0),
pos2: SCNVector3(x: 8, y: 5, z: 0),
pos3: SCNVector3(x:-8, y: 5, z: 0))
Normals' direction issue
Usually a model has 3 normals per each triangular face, or in other words, one normal at each vertex of every face. Normals must be perpendicular to a surface, not parallel. If you have issues with surface normals, then you won't be able to light the model.
Related Topics
How to Cut a Hole in a Sprite Image or Texture to Show What Is Behind It Using Spritekit in Swift
Understanding UIviewrepresentable
Nsnumber/Nsdecimalnumber Bizarre Behavior
Issue with Optional Core Data Relationship Using Nspersistentcloudkitcontainer
How to Name File Stored to Files App via Uactivityviewcontroller
Xcode Swift: Could Not Insert New Outlet Connection
What Happens While Wrapping and Unwrapping an Optional in Swift
Outlets Cannot Be Connected to Repeating Content
Expose an Interface of a Class Loaded from a Framework at Runtime
Prefix(_ Maxlength:) Is Type-Erased When Used with a Struct That Conforms to Lazysequenceprotocol
How to Insert UIlabel After UItextfield in UIalertcontroller
How to Have a Searchbar Which Shows Suggestions with Different UItableview
Generating a Simple Algebraic Expression in Swift
Having Trouble with Musickit Sample App Provided by Apple
Navigationview Inside a Tabview Swift UI
Make Statement More Clear: Checking Object for Key in Dictionary