In scene kit, adding a physics body manually (in code) to a node, seems to not work? You have to add the physics body in the editor? - scenekit

It seems you just can't add a physics body in code.
Not much more can be said than this, has anyone found this issue?
Some example code ...
class SomeSceneView: SCNView {
public var box: SCNNode!
..
var physBody = SCNPhysicsBody()
private func bringup() {
box = scene?.rootNode.childNode(withName: "box", recursively: true)
physBody.type = .dynamic
physBody.isAffectedByGravity = false
physBody.friction = 0
box.physicsBody = physBody
rendersContinuously = true
play(nil)
}
}
Any experience with this?

You need also to file a SCNPhysicsShape.
Try something like this:
func makeNode() {
let node = SCNNode(geometry: SCNSphere(radius: 1.0))
...
node.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: node.geometry, options: nil))
...
scene.rootnode.addChildNode(node)
}

Related

SwiftUI remove data into a structured array via a button in a ForEach

I'm trying to remove a line in my structured array when user click on the delete button. But as I use a foreach to load all my array lines into a specific subview I don't know how to pass the index of the ForEach into my subview to delete my line...
My code is like this,
ScrollView{
VStack {
ForEach(planeLibrary.testPlane){plane in
ZStack {
RoundedRectangle(cornerRadius: 16, style: .continuous)
.fill(Color.white)
.shadow(color: Color(Color.RGBColorSpace.sRGB, white: 0, opacity: 0.2), radius: 4)
PlaneCellView(plane: plane, planeLibrary: planeLibrary, line: ???)
}
}
}.padding(.horizontal, 16)
}
And my PlaneCellView :
#State var plane: Plane
#ObservedObject var planeLibrary: PlaneLibrary
var line: Int
var body: some View {
//...
VStack(alignment: .leading) {
Text(plane.planeImat)
.font(.title)
.fontWeight(.bold)
Text(plane.planeType)
HStack{
Text(plane.isSe ? "SE" : "ME")
Text(plane.isNight ? "- Night" : "")
Text(plane.isIfr ? "- IFR" : "")
}
}
Spacer()
Button {
// HERE I don't know how to delete my array line ...
planeLibrary.testPlane.remove(at: line)
} label: {
Image(systemName: "trash.circle")
.foregroundColor(.red)
.font(.system(size: 30))
}
//...
}
My Plane library :
struct Plane: Identifiable{
let id = UUID().uuidString
let planeImat: String
let planeType: String
let isSe: Bool
let isIfr: Bool
let isNight: Bool
let autoID: String
init (planeImat: String, planeType: String, isSe: Bool, isIfr: Bool, isNight: Bool, autoID: String){
self.planeType = planeType
self.planeImat = planeImat
self.isSe = isSe
self.isIfr = isIfr
self.isNight = isNight
self.autoID = autoID
}
init(config: NewPlaneConfig){
self.planeImat = config.imat
self.planeType = config.type
self.isSe = config.isSe
self.isIfr = config.isIfr
self.isNight = config.isNight
self.autoID = config.autoID
}
}
I've already try to add id: \.self as I was able to find on this forum but without any success.
You haven't actually included PlaneLibrary, so I will assume that planeLibrary.testPlane is an array of Plane structs.
There are many ways of solving this, including changing testPlane to be a Dictionary of Plane structs (indexed by id), or if order is important, in an OrderedDictionary (add the swift-collections package to your project and import OrderedCollections in the file where it is used). You could use testPlane.removeValue(at: id) to remove the plane from either type of dictionary.
If you keep it as an array, but your array might be large and you're worried about run-time efficiency, the best thing to do is to change your ForEach to include the index of the planes in the loop.
It would look something like this:
ForEach(Array(planeLibrary.testPlane.enumerated()), id: \.element.id) { index, plane in
// In this code you can use either plane, or index.
...
// UI code
Text(plane.autoID)
...
{ // remove closure
planeLibrary.testPlane.remove(at: index)
}
}
But if the array is of reasonable size, you could keep it as it is now and use testPlane.remove(where:) to find it by id at the time of deletion. The code for this is much simpler and easier to read and understand, so it should probably be your first choice. Optimise for large lists later, if you need.
You can't pass the index in because that will crash the ForEach View. Instead, look up its index using its ID afterwards to remove it, e.g.
class RecipeBox: ObservableObject {
#Published var allRecipes: [Recipe]
#Published var collections: [String]
...
func delete(_ recipe: Recipe) {
delete(recipe.id)
}
func delete(_ id: Recipe.ID) {
if let index = index(for: id) {
allRecipes.remove(at: index)
updateCollectionsIfNeeded()
}
}
...
func index(for id: Recipe.ID) -> Int? {
allRecipes.firstIndex(where: { $0.id == id })
}
...
This sample is from Defining the source of truth using a custom binding (Apple Developer)

How to Save an Array of Custom Objects All At Once to a Core Data Store in Swift

I have a very similar question to:
Swift: Store arrays of custom classes in Core Data
The difference is that question was looking to add to a custom class array in Core Data one element at a time. I am looking to add the entire array at once. When I try to, I get the following error:
[CDTester.MassOfSubpart entity]: unrecognized selector sent to instance.
I made a simple app to demonstrate the error and hopefully figure out where I am going wrong. I uploaded it to GitHub here: https://github.com/Yrban/Core-Data-Test-App
The code where it crashes is here:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
var massPerSubpartMeasurementArray: [MassOfSubpart]
massPerSubpartMeasurementArray = massOfSubpartMeasurementDict.map {
return MassOfSubpart(subpart: $0.key, mass: Measurement(value: Double($0.value)!, unit: self.massUnit))
}
let massOfSubpartSet = Set(massPerSubpartMeasurementArray)
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.hasMassOfPart = massOfSubpartSet // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
My Core Data model is:
MassOfSubpart is a custom object:
import SwiftUI
public class MassOfSubpart: NSObject, Codable {
var subPart: Int
var mass: Measurement<UnitMass>
override init() {
self.subPart = 1
self.mass = Measurement(value: 0.0, unit: UnitMass.grams)
}
init(subpart: Int, mass: Measurement<UnitMass>){
self.subPart = subpart
self.mass = mass
}
}
I have read everything I could find, including Apple's documentation. I feel I am close, but I am obviously missing something important. Any help would be appreciated.
I finally figured out my error. I have not used relationships before, so it didn't dawn on me what I was doing. When I was accessing MassOfPartEntity through its relationship with Widget, I was sending a Set, my custom object through. However, I had to send a Set, the actual entity through. Therefore, I changed saveWidget to this:
func saveWidget(){
print("saveWidget() entered")
if self.massOfSubpartMeasurementDict.count > 0,
self.massOfSubpartMeasurementDict.count == self.numberOfSubparts,
self.manufacturer != "",
self.title != "" {
let massOfPartEntity = MassOfPartEntity(context: self.managedObjectContext)
var massPerSubpartEntityArray: [MassOfPartEntity]
massPerSubpartEntityArray = massOfSubpartMeasurementDict.map {
massOfPartEntity.subPart = Int16($0.key)
if let massDouble = Double($0.value) {
massOfPartEntity.mass = Measurement(value: massDouble, unit: massUnit)
} else {
massOfPartEntity.mass = Measurement(value: 0.0, unit: massUnit)
}
return massOfPartEntity
}
let massOfSubpartSet = Set(massPerSubpartEntityArray) as NSSet
let widget = Widget(context: self.managedObjectContext)
widget.id = UUID()
widget.madeBy?.name = self.manufacturer
widget.addToHasMassOfPart(massOfSubpartSet) // FIXME: The crash is here
widget.title = self.title
do {
print("SaveWidget() attempted/n")
try self.managedObjectContext.save()
} catch {
print("SaveWidget() failed/n")
// handle the Core Data error
// Show alert as to status
}
}
}
changing the Map function return so that it returned MassOfPartEntity's that eventually became a Set. Essentially, it was a type mismatch.
Thanks to those who responded as talking it out definitely helped resolve this.

SceneKit and using a custom camera class

I am trying to create a custom Camera class to reuse across all levels in SceneKit.
I have defined the cameraNode
Set the sceneView to use the cameraNode pointOfView
Define class:
class GameCamera: SCNCamera {
let cameraNodeHorizontal: SCNNode!
override init() {
cameraNodeHorizontal = SCNScene(named: "/GameAssets.scnassets/Camera.scn")?.rootNode.childNode(withName: "GameCamera", recursively: true)
super.init()
}
func setup(scnView: SCNView) {
scnView.scene?.rootNode.addChildNode(cameraNodeHorizontal)
scnView.pointOfView = cameraNodeHorizontal
}
}
Inside ViewController:
private var camera = GameCamera()
private func loadCamera() {
camera.setup(scnView: self.scnView)
}
The scene renders from a default pointOfView other than the one I defined.
Wondering if anyone can help?
I don't use .scn - but just a basic class, something like this:
var cameraEye = SCNNode()
var cameraFocus = SCNNode()
init()
{
cameraEye.name = "Camera Eye"
cameraFocus.name = "Camera Focus"
cameraFocus.isHidden = true
cameraFocus.position = SCNVector3(x: 0, y: 0, z: 0)
cameraEye.camera = SCNCamera()
cameraEye.constraints = []
cameraEye.position = SCNVector3(x: 0, y: 15, z: 0.1)
let vConstraint = SCNLookAtConstraint(target: cameraFocus)
vConstraint.isGimbalLockEnabled = true
cameraEye.constraints = [vConstraint]
}
// Add camera and focus nodes to your Scenekit nodes
gameNodes.addChildNode(camera.cameraEye)
gameNodes.addChildNode(camera.cameraFocus)

Updating element values of an array in swift

I have a lot of experience working with Matlab, but I only recently started programming in Swift 4. My current project involves building a questionnaire. I have used the ‘drag and drop’ feature in Xcode to produce an #IBAction function for a button in storyboard, which can then lead to pressed button changing its appearance. This functionality is contained within the ButtonResponse class in the code snippet below:
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(_ anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse: UIButton {
var responseVariables: ResponseProfile
var checkedImage = UIImage(named: "checkedResponseBox")! as UIImage
var uncheckedImage = UIImage(named: "uncheckedResponseBox")! as UIImage
required init?(coder aDecoder: NSCoder) {
self.responseVariables = ResponseProfile(
responseArray: []
)
super.init(coder: aDecoder)
}
#IBAction func checkboxTapped(_ sender: UIButton) {
switch sender.accessibilityIdentifier {
case "excellent":
let oldResponseStatus = responseVariables.responseArray[0]
if oldResponseStatus == "unchecked"{
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[0] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[0] = "unchecked"
}
case "veryGood":
let oldResponseStatus = responseVariables.responseArray[1]
if oldResponseStatus == "unchecked" {
sender.setImage(checkedImage, for: UIControlState.normal)
let oldResponsePresence = responseVariables.responseArray.contains("checked")
if oldResponsePresence == true {
responseVariables.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
responseVariables.responseArray[1] = "checked"
} else if oldResponseStatus == "checked" {
sender.setImage(uncheckedImage, for: UIControlState.normal)
responseVariables.responseArray[1] = "unchecked"
}
default: break
}
}
}
I imagined that I could use an array to internally represent the state of the buttons in the user interface (this would be the ‘responseArray’ variable). By changing elements within responseArray following a button press, I thought I could keep track which buttons were pressed and ensure that no more than one button at a time was checked. I incorrectly thought responseArray would be updated, but this is not the case. The array always reverts to its initiation state.
N.B. responseArray contains seven elements because there are seven response options. So far, I have attempted to program only two of the response options: “excellent” and “veryGood”.
In attempting to find a solution, I attempted to simplify the above code in playground:
import UIKit
struct ResponseProfile {
var responseArray: Array<String>
init(responseArray: Array<String>) {
self.responseArray = ["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
}
mutating func updateArray(input anArray: Array<String>) -> (Array<String>) {
responseArray = anArray
return responseArray
}
}
class ButtonResponse {
var responseVariables: ResponseProfile
init(){
self.responseVariables = ResponseProfile(responseArray: [])
}
var responseA = ResponseProfile(responseArray: [])
}
var responseOne = ResponseProfile(responseArray: [])
responseOne.responseArray[0] = "checked" //user performs action resulting in first element being changed from a starting value of "unchecked" to "checked"
responseOne.updateArray(input: responseOne.responseArray)
var responseTwo = ResponseProfile(responseArray:[])
responseTwo.responseArray //responseArray revert to initialization values. How can I keep changes to the responseArray?
How can I update responseArray within the ResponseProfile structure without having to create a new variable to record every change? Is this the problem I should be looking at or is there, on a more general level, a better strategy that I should be taking?
I am surprised that I struggled this much to deal with this issue. I thought the answer would be clear if I read the relevant parts of the documentation and studied some example code. All the example code I found was too simplistic and focused on just one iteration of updating the array.
Any comments or suggestions would be much appreciated!
Looking at your playground code, I found that you are passing a blank [] array to argument of ResponseProfile struct during init. and it is always initialising your responseArray.
If you want to pass the things by reference, you can change Response profile to class
and there you can achieve the similar functionalities and use inout parameter to keep the same array without using the function updateArray.
The example I am showing here is for the class and objects of class can be pass by reference. thus keep your previous changes.
var responseTwo = ResponseProfile(responseArray:[])
If you wants to keep the old response, you can pass that array as an argument
var responseTwo = ResponseProfile(responseArray:responseOne.responseArray)
OR
var responseTwo = responseOne
Will keep the responseArray.
You can read more about it, at official blog
Also you can this post with more insight for the case.
Hope it helps.
Thanks for your response Bhavin. By passing responseArray by reference (as Bhavin suggests) to the necessary class (which has turned out to be the ButtonResponse class rather than ResponseProfile), I can give responseArray an initial value. I then use the buttonPress function to update responseArray. See below:
class ButtonResponse: Responses {
var responseArray: [String]
init (responseArray: [String]) {
self.responseArray = responseArray
}
func buttonPress(inputString: String, targetIndex: Int) -> [String] {
//need guard statement to block function when targetIndex > number of elements in responseArray
responseArray[targetIndex] = inputString
return responseArray
}
}
let initiateResponseArray =
["unchecked","unchecked","unchecked","unchecked","unchecked","unchecked","unchecked"]
var doPress = ButtonResponse(responseArray: initiateResponseArray)
doPress.buttonPress(inputString: "checked", targetIndex: 0)
var getPressInfo1 = doPress.responseArray
print(getPressInfo1)
//prints: ["checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
doPress.buttonPress(inputString: "checked", targetIndex: 1)
var getPressInfo2 = doPress.responseArray
print(getPressInfo2)
//prints: ["checked", "checked", "unchecked", "unchecked", "unchecked", "unchecked", "unchecked"]
I am still unsure how to implement this solution in the project I am working on. I will create a separate question for this because it seems to raise different issues.

Using Vuforia provided Projection Matrix and Marker Pose in SceneKit

Currently I'm trying to troubleshoot the use of the projection matrix and framemarker pose when rendering in SceneKit. The model in the scene and the camera image background appear without problems. However, once I change the projection matrix and framemarker pose matrix to match Vuforia everything is pushed offscreen.
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
let camera = self.cameraNode?.camera
camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4Invert(SCNMatrix4FromMat4(framemarker.pose))
self.objectNode?.transform = pose
}
}
Is this a correct way to setup the SCNCamera and object node and is there anything else required to setup Vuforia framemarkers to work with SceneKit?
It just works!
The hard part is determining what pieces of SceneKit are necessary to make this work. Originally I read the article Making Augmented Reality app easily with Scenekit + Vuforia which outlined how to rejigger the sample app for user-defined targets. The downsides to that article include that it isn't always clear what the author changed, no sample project is provided, and it is based upon an older version of Vuforia. Ultimately, I found it unnecessary to invert the pose matrix.
Draw camera image and set projection matrix and update marker pose
override func viewDidLoad()
{
super.viewDidLoad()
let scene = SmartScanScene()
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
scene.rootNode.addChildNode(cameraNode)
_cameraNode = cameraNode
let view = self.view as! SCNView
view.backgroundColor = UIColor.blackColor()
view.showsStatistics = true
// view.debugOptions = SCNDebugOptions.ShowBoundingBoxes.union(.ShowWireframe)
view.autoenablesDefaultLighting = true
view.allowsCameraControl = false
}
func didUpdateProjectionMatrix(projectionMatrix: matrix_float4x4)
{
let extrinsic = SCNMatrix4FromMat4(projectionMatrix)
_cameraNode?.camera?.setProjectionTransform(extrinsic)
}
func didUpdateFramemarkers(framemarkers: [Framemarker]?)
{
guard let framemarkers = framemarkers else {
return
}
for framemarker in framemarkers {
let pose = SCNMatrix4FromMat4(framemarker.pose)
self.objectNode?.transform = pose
}
}
func didUpdateCameraImage(image: UIImage?)
{
if let image = image {
_scene?.background.contents = image
}
}

Resources