How to Disabled Some Default Functionality in Scene View When Allowscameracontrol = True

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 @Bindingto set its active state (not sure about the correct order for this with the@State property inHUDView`):

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.

Sample Image

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.

Sample Image



Related Topics



Leave a reply



Submit