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!
Related
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.
I have a camerNode position issue. I have included the code below + an attempt of resolving this with no progress. Essentially in my GameScene I have the camera locked perfectly onto the player when moving through the scene, I am actually trying to amend this so that the camera is slightly ahead of the player, this meaning my player is actually positioned on the left side of the screen, almost like an offset (+ 200) :) if this existed.
The Code:
class GameScene: SKScene, SKPhysicsContactDelegate {
// Create a constant cam as a SKCameraNode:
let cam = SKCameraNode()
override func didMove(to view: SKView) {
// vertical center of the screen:
screenCenterY = self.size.height / 2
// Assign the camera to the scene
self.camera = cam
//Add the camera itself to the scene's node tree"
self.addChild(self.camera!)
// Position the camera node above the game elements:
self.camera!.zPosition = 50
}
override func didSimulatePhysics() {
// Keep the camera locked at mid screen by default:
var cameraYPos = screenCenterY
cam.yScale = 1
cam.xScale = 1
// Follow the player:
if (player.position.y > screenCenterY) {
cameraYPos = player.position.y
cam.yScale = newScale
cam.xScale = newScale
}
// Camera Adjustment:
self.camera!.position = CGPoint(x: player.position.x, y: cameraYPos)
I initially thought that I could overcome this by changing the player to another SKSpriteNode.. i'e in my HUD class I could add a node and apply this code around? I could then refer back to my player class in which I already defined the position above my override func didMove.
let initialPlayerPosition = CGPoint(x: 50, y: 350)
I did try this and the GameScene started playing up, is there a better method for achieving this result? I assume the code could be changed to accommodate but I am still in the learning grounds of Xcode.
Never mind - solved it!
Camera adjustment statement - player.position.x + 200) :)
This does the trick!
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)
I am trying to create a object that can be dragged and rotated in an NSView and have been successful in doing so using NSBezierPath. I am creating multiple objects and storing them in a class and using NSBezierPath.transform(using: AffineTransform) to modify the path in response to drag and rotation inputs.
This all works fine but I now want to add text to the shape and it seems there are a different set of rules for dealing with text.
I have tried using Core Text by creating a CTFrame but have no idea how to move or rotate this.
Is there a good reason for why the handling of text is so different from NSBezierPath.
And then there is the difference between AffineTransform and CGAffineTransform. The whole thing is pretty confusing and good documentation explaining the difference seems hard to come by.
Below is the code for creating and moving the shape which seems work perfectly. I have no idea how to move the text, ideally without having to recreate it. Is there any way to translate and rotate the CTFrame?
var path: NSBezierPath
var location: NSPoint {
didSet {
// move()
}
}
var angle: CGFloat {
didSet {
let dx = angle - oldValue
rotate(dx)
}
}
func createPath(){
// Create a simple path with a rectangle
self.path = NSBezierPath(rect: NSRect(x: -1*width/2.0, y: -1*height/2.0, width: width, height: height))
let line = NSBezierPath()
line.move(to: NSPoint(x: width/2.0, y:0))
line.line(to: NSPoint(x: width/2.0+leader, y:0))
self.path.append(line)
// Label !!
let rect = NSRect(x: width/2.0, y: 0, width: leader, height: height/2.0)
let attrString = NSAttributedString(string: assortmentLabel, attributes: attributesForLeftText)
self.labelFrame = textFrame(attrString: attrString, rect: rect)
// ??? How to rotate the CTFrame - is this even possible
move()
rotate(angle)
}
func rotate(_ da: CGFloat){
// Move to origin
let loc = AffineTransform(translationByX: -location.x, byY: -location.y)
self.path.transform(using: loc)
let rotation = AffineTransform(rotationByDegrees: da)
self.path.transform(using: rotation)
// Move back
self.path.transform(using: AffineTransform(translationByX: location.x, byY: location.y))
}
func move(){
let loc = AffineTransform(translationByX: location.x, byY: location.y)
self.path.transform(using: loc)
}
func draw(){
guard let context = NSGraphicsContext.current?.cgContext else {
return
}
color.set()
path.stroke()
if isSelected {
path.fill()
}
if let frame = self.labelFrame {
CTFrameDraw(frame, context)
}
}
// -----------------------------------
// Modify the item location
// -----------------------------------
func offsetLocationBy(x: CGFloat, y:CGFloat)
{
location.x=location.x+x
location.y=location.y+y
let loc = AffineTransform(translationByX: x, byY: y)
self.path.transform(using: loc)
}
EDIT:
I have changed things around a bit to now draw the shape at origin 0,0 and to then apply the transformation to CGContext prior to drawing the shape.
This does the job and using CTDrawFrame now works correctly...
Well almost...
On my test app it works perfectly but when I integrated the exact same code to the production app the text appears upside-down and all characters shown on top of each other.
As far as I can tell there is nothing different about the views the drawing is taking place in - uses the same NSView subclass.
Is there something else that could upset the drawing of the text - seems like an isFlipped issue but why would this happen in the one app and not the other.
Everything else seems to draw correctly. Tearing my hair out on this.
After much struggling it seems I got lucky with the test app working at all and needed to set the textMatrix on BOTH apps to ensure things work properly under all conditions.
It also seems one can't create the CTFrame and later just scale it and redraw it - well it didn't work for me so I had to recreate it in the draw() method each time!
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?