Unable to Load 3D Object from External File with SceneKit on macOS - macos

I would like to load a 3D object from an external file, with an extension like .obj, .dae or .abc, into a SCNView view and I want to do it programmatically, so the user decides which object is loaded. I'm doing it in this way:
#IBOutlet weak var scnView: SCNView!
scnView.scene = SCNScene()
let openPanel = NSOpenPanel()
openPanel.allowedFileTypes = ["obj", "dae", "abc"]
openPanel.begin { (result) in
if result == .OK, let docURL = openPanel.urls.first {
let scene = try! SCNScene(url: docURL, options: nil)
self.scnView.scene?.rootNode.addChildNode(scene.rootNode.clone())
}
}
When I load a 3D object with a .obj extension, the scene object is correctly populated with the 3D model, but the only thing I can see is this message from the console:
2017-12-17 21:27:39.734165+0100 DemoApp[15244:959862] Unable to projec to 2D plane
What I'm doing wrong?

Related

How to compute MKMapCamera centerCoordinateDistance programmatically in SwiftUI

I have a SwiftUI MapKitView that follows the pattern in https://www.hackingwithswift.com/books/ios-swiftui/advanced-mkmapview-with-swiftui adapted for macOS. In makeNSView I set the region for the interesting bit of the location I want to display, irrelevant of windows size and I can get this code to zoom appropriately by default whether the NSWindow is more landscape than portrait or vice versa.
func makeNSView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.mapType = .satellite
mapView.pointOfInterestFilter = .excludingAll
let region = MKCoordinateRegion(center: centroid, latitudinalMeters: 1160, longitudinalMeters: 1260)
let fittedRegion = mapView.regionThatFits(region)
mapView.setRegion(fittedRegion, animated: false)
}
It appears thatmapView.region is not actually updated upon the return of setRegion()
The rub is I want to orient the map other than true north, so I have to set a camera.
However, the fromDistance: parameter in creating MKMapCamera has to be computed from the region that was set, but what is the camera's field of view angle to determine how high it needs to be to include the correct extent for the window once the region is set? I basically want the zoom level set the same as via the fittedRegion and want to replicate that in the camera with the changed heading (with pitch at 0)
It appears the MKMapViewDelegate has a mapViewDidChangeVisibleRegion and I think the Coordinator is the delegate in SwiftUI. I can see the region on multiple calls to updateNSView, tho it takes a few calls before its actually set. I suspect setting the camera there will create another updateNSView() call which would pose problems.
How can I orient the map to include a given region extent regardless of window size with the zoom level and heading on initial load (but then lets the user manipulate as they see fit)..
Try the following
func makeNSView(context: Context) -> MKMapView {
let mapView = MKMapView()
mapView.delegate = context.coordinator
mapView.mapType = .satellite
mapView.pointOfInterestFilter = .excludingAll
let region = MKCoordinateRegion(center: centroid, latitudinalMeters: 1160, longitudinalMeters: 1260)
let fittedRegion = mapView.regionThatFits(region)
DispatchQueue.main.async {
mapView.setRegion(fittedRegion, animated: false)
}
return mapView
}
I think I figured it out.
Responding to func mapViewDidFinishLoadingMap(_ mapView: MKMapView) in the Coordinator and setting the mapView.camera.heading to an appropriate value there does what I'm intending.

SCNView exported Collada (.dae) file looks different and impossible to import to Blender

I have a problem with exporting scene from SCNView.
In Xcode I made simple scene with light and one big, transparent box.
Colourful small boxes are generated inside running application.
Scene in SCNView looks like this:
I'm happy with this.
But if I export file by
#IBAction func exportDAE(_ sender: Any) {
if let scene = view3D.scene {
let panel = NSSavePanel()
panel.allowedFileTypes = ["dae"]
panel.runModal()
if let url = panel.url {
scene.write(to: url,
options: nil,
delegate: nil,
progressHandler: nil);
}
}
}
An error is thrown:
2019-12-02 13:27:16.450749+0100 ShowMeInstances[83949:14810790] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}
all small boxes are destroyed, only one big looks OK:
Sometimes materials are broken too: (those magenta destroyed boxes should have similar color to surrounded boxes:
In AppDelegate I defined:
var instancesNode: InstancesNode? = nil
which is inited by:
class InstancesNode: SCNNode {
typealias CoordUnit = Double
var instanceGenerator: InstanceGenerator<CoordUnit>?
init(with data: Data) throws {
self.instanceGenerator = try InstanceGenerator(from: data)
super.init()
for instance in instanceGenerator?.instances ?? [] {
let coords = instance.coordinates.map( {$0.value})
let color = getMaterialDiffuseColor(for: coords)
let coords3D = convertCoordinatesTo3D(coords)
let node = Instance3D(
name: instance.instanceName,
coordinates: coords3D,
color: color)
self.addChildNode(node)
}
}
.....
}
now Instance3D is inited by
class Instance3D: SCNNode {
init (name: String, coordinates: SCNVector3, color: NSColor) {
super.init()
self.name = name
let box = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
let material = SCNMaterial()
material.diffuse.contents = color
material.emission.contents = color
material.lightingModel = .lambert
material.metalness.contents = 0
material.shininess = 1
box.materials = [material]
self.position = coordinates
self.geometry = box
}
Everything is inserted into scene by
view3D.scene?.rootNode.addChildNode(instancesNode!)
in appDelegate. Without this line only big box and light are visible in view.
Also file cannot be imported by Blender, Blender silently and immediately explodes.
What could be a reason? Is it a feature or is it a bug?
When I generate just one Instance, generated box is OK, can import file to Blender, if two — only one is OK, Blender explodes. If i change box to sphere — one one sphere is visible in QuickLook
Definitely bug.
I can confirm. The following code, once exported from SceneKit in .dae format will crash Blender (and will cause an error in Meshlab, and not render correctly in Mac OS X's finder preview).
var radius = 0.0005
let s1 = SCNNode(geometry: SCNSphere(radius: CGFloat(radius)))
let s2 = SCNNode(geometry: SCNSphere(radius: CGFloat(radius)))
s1.position = SCNVector3(0,0,0)
s2.position = SCNVector3(1,0,0)
scene.rootNode.addChildNode(s1)
scene.rootNode.addChildNode(s2)

SceneKit load animation from .dae to model in scene

If anyone can help me, I would appreciate it :)
Using SceneKit, what I am trying to achieve is to keep t-pose character model files and animation files separate so that I can load them on runtime. I have prepared my model files, which include both mesh and rig in their respective root nodes. I have also prepared the animation .dae files. These are all created in Blender and animations are exported from mixamo. Animation .dae files only contain rig nodes. I have successfully loaded them into my scene and applied the animation selected.
This is my folder structure: Screen Shot
And I have tried to do it like the following:
guard let scene = SCNScene(named: "art.scnassets/bodies/model_tpose.scn") else { return }
// Create a mount node
let mountNode = SCNNode()
// Find and add model body and rig nodes
guard let body = scene.rootNode.childNode(withName: "Body", recursively: true) else { return }
guard let rig = scene.rootNode.childNode(withName: "mixamorig_Hips", recursively: true) else { return }
mountNode.addChildNode(body)
mountNode.addChildNode(rig)
// add mount node
addChildNode(mountNode)
guard let animationUrl = Bundle.main.url(forResource: "art.scnassets/animations/falling", withExtension: "dae") else { return }
let src = SCNSceneSource(url: animationUrl, options: nil)
guard let animation = src?.entryWithIdentifier("mixamorig_Hips-anim", withClass: CAAnimation.self) else { return }
animation.fadeInDuration = 0.2
animation.fadeOutDuration = 0.2
animation.usesSceneTimeBase = false
animation.repeatCount = .greatestFiniteMagnitude
rig.addAnimation(animation, forKey: "current_animation")
The issue is that the animation doesn't deform the body mesh, it only animates its position and rotation. Only the node position and rotation animations are updated. I suspect that I am missing something related to skinning by SCNSkinner
Please don't mind the different file names in ScreenShot and the code above, i was just trying to show information about my issue rapidly.
Thanks a ton in advance!

Swift - Get mouse click coordinates within a UIElement

I'm making a Cocoa app for OS X using Swift. The app is supposed to be very simple: basically it displays an image, then listens for mouse clicks within that image and returns the 2D mouse coords based on the origin of the image (not the absolute mouse coordinates). I'm not sure if I described that well, but for example once it registers a mouse click event, it should tell me that the mouse click occurred 23 pixels to the right and 57 pixels down from the 0,0 point of the image (or whatever the units would be).
So far I have this, but all I've been able to do is get it to return the absolute mouse coordinates:
import Cocoa
class ViewController: NSViewController {
#IBOutlet var ImageButton: NSButton!
override func viewDidLoad() {
super.viewDidLoad()
let fileName = "myTestImage.jpg"
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! + "/TrainingSet/" + fileName
//"/Users/dan/Documents/myTestImage.jpg"
let myImage = NSImage(contentsOfFile: path)
ImageButton.image = myImage
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func ImageButtonClicked(sender: NSButton) {
//let x = sender
//let coords = x.frame
let mouseLocation = NSEvent.mouseLocation();
print( "Mouse Location X,Y = \(mouseLocation)" )
print( "Mouse Location X = \(mouseLocation.x)" )
print( "Mouse Location Y = \(mouseLocation.y)" )
}
}
How would I go about getting the information I need?
What about
sender.convertPoint(mouseLocation, fromView:nil)

Changing scene causes zoom

When going from GameScene to PlayScene using this code below, all works fine:
for touch: AnyObject in touches {
let location = touch.locationInNode(self)
if self.nodeAtPoint(location) == self.playButton1 {
var scene = PlayScene(size: self.size)
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
}
But when i am going from PlayScene to GameScene using this code below, it gets zoomed in:
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
Tried to Updated code:
var scene = GameScene(size: self.size)
let skView = self.view as SKView!
skView.ignoresSiblingOrder = true
scene.scaleMode = .ResizeFill
scene.size = skView.bounds.size
skView.presentScene(scene)
}
How come? Any suggestions?
In the first code snippet, you create the new scene of self.size which keeps the same size as the old one. But in the second one, you use another method to do that, and the size of the new scene is initialized from the file GameScene.sks whose size is 1024x768 by default.
EDIT:
Did you use
if let scene = GameScene.unarchiveFromFile("GameScene") as? GameScene {...}
to create your first scene in your GameViewController.swift? If so, try to replace this line with
let scene = GameScene(size: view.bounds.size)
I'm afraid the reason why your buttons get resized(zoomed) is that they are not in the right size at the first time you load GameScene, actually they are in a scene sized 1024x768. Your button is hard-coded as 400 width which is nearly the 1/3 of the scene width (1024pt). When you load GameScene again, its width now becomes the screen width, so you find buttons get resized. Print scene.size.width for more info.
I had the same problem.
For me the problem was that the scene size had changed to be much smaller when going back to the first scene.
Scene 1) GameMap Scene
Original Scene Size: (1024.0, 4096.0)
Original View Size : ( 320.0, 480.0)
Scene 2) GameLevel Scene
Original Scene Size: ( 320.0, 480.0)
Original View Size : ( 320.0, 480.0)
When Moving from Scene 1 to Scene 2 everything looked fine but when moving back from Scene 2 to Scene 1 The "Scene 1" size is changed to Scene 2's size
Scene 1) GameMap Scene -> After Going Back
Original Scene Size: ( 320.0, 480.0)
Original View Size : ( 320.0, 480.0)
My solution was to specifically change the size of the scene in "didMoveToView" and set the scaleMode of the "Scene 1" see below:
self.view?.bounds = CGRectMake(0, 0, Constants.screenWidth, Constants.screenHeight)
self.scene?.size = CGSizeMake(1024, 4096)
self.scaleMode = SKSceneScaleMode.AspectFill
Hope it helps
BR
BuD

Resources