I'm working on an application with Arkit. There are many 3D models and the size is big in my app. Can I get these models out of another server (outside sites)? I'm new on swift, I can't seem to find anything on loading a 3d model from a web server.
is it enough to change the model path there? Thank you
func loadModel() {
guard let virtualObjectScene = SCNScene(named: "\(modelName).\(fileExtension)", inDirectory: "Models.scnassets/\(modelName)") else {
return
}
let wrapperNode = SCNNode()
for child in virtualObjectScene.rootNode.childNodes {
let defaults = UserDefaults.standard
wrapperNode.addChildNode(child)
}
self.addChildNode(wrapperNode)
}
All code:
import UIKit
import SceneKit
import ARKit
class VirtualObject: SCNNode {
var modelName: String = ""
var fileExtension: String = ""
var thumbImage: UIImage!
var title: String = ""
var viewController: ViewController?
override init() {
super.init()
self.name = "Virtual object root node"
}
init(modelName: String, fileExtension: String, thumbImageFilename: String, title: String) {
super.init()
self.name = "Virtual object root node"
self.modelName = modelName
self.fileExtension = fileExtension
self.thumbImage = UIImage(named: thumbImageFilename)
self.title = title
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func loadModel() {
guard let virtualObjectScene = SCNScene(named: "\(modelName).\(fileExtension)", inDirectory: "Models.scnassets/\(modelName)") else {
return
}
let wrapperNode = SCNNode()
for child in virtualObjectScene.rootNode.childNodes {
let defaults = UserDefaults.standard
wrapperNode.addChildNode(child)
}
self.addChildNode(wrapperNode)
}
func unloadModel() {
self.removeFromParentNode()
for child in self.childNodes {
child.removeFromParentNode()
}
}
func translateBasedOnScreenPos(_ pos: CGPoint, instantly: Bool, infinitePlane: Bool) {
guard let controller = viewController else {
return
}
let result = controller.worldPositionFromScreenPosition(pos, objectPos: self.position, infinitePlane: infinitePlane)
controller.moveVirtualObjectToPosition(result.position, instantly, !result.hitAPlane)
}
}
extension VirtualObject {
static func isNodePartOfVirtualObject(_ node: SCNNode) -> Bool {
if node.name == "Virtual object root node" {
return true
}
if node.parent != nil {
return isNodePartOfVirtualObject(node.parent!)
}
return false
}
static let availableObjects: [VirtualObject] = [
Anatomy()
]
}
you can load an scn file from a webserver with ip addresses like this (i used a fake ip below)
let myURL = NSURL(string: “http://110.151.153.202:80/scnfiles/myfile.scn”)
let scene = try! SCNScene(url: myURL! as URL, options:nil)
Edit:
Here’s a simple Swift PlayGrounds which pulls a test cube scn file from my github repo. You just tap anywhere and the cube loads.
import ARKit
import SceneKit
import PlaygroundSupport
class ViewController: NSObject {
var sceneView: ARSCNView
init(sceneView: ARSCNView) {
self.sceneView = sceneView
super.init()
self.setupWorldTracking()
self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(ViewController.handleTap(_:))))
}
private func setupWorldTracking() {
if ARWorldTrackingConfiguration.isSupported {
let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
configuration.isLightEstimationEnabled = true
self.sceneView.session.run(configuration, options: [])
}
}
#objc func handleTap(_ gesture: UITapGestureRecognizer) {
let results = self.sceneView.hitTest(gesture.location(in: gesture.view), types: ARHitTestResult.ResultType.featurePoint)
guard let result: ARHitTestResult = results.first else {
return
}
// pulls cube.scn from github repo
let myURL = NSURL(string: "https://raw.githubusercontent.com/wave-electron/scnFile/master/cube.scn")
let scene = try! SCNScene(url: myURL! as URL, options: nil)
let node = scene.rootNode.childNode(withName: "SketchUp", recursively: true)
node?.scale = SCNVector3(0.01,0.01,0.01)
let position = SCNVector3Make(result.worldTransform.columns.3.x, result.worldTransform.columns.3.y, result.worldTransform.columns.3.z)
node?.position = position
self.sceneView.scene.rootNode.addChildNode(node!)
}
}
let sceneView = ARSCNView()
let viewController = ViewController(sceneView: sceneView)
sceneView.autoenablesDefaultLighting = true
PlaygroundPage.current.needsIndefiniteExecution = true
PlaygroundPage.current.liveView = viewController.sceneView
Related
I loaded into SceneKit a .usdz file which has an animation attached to it. I want to stop this animation to play.. but I can't find the right way.
I load the .usdz asset file with the following method:
func loadIdle() {
let urlfile = Bundle.main.path(forResource: "armi_idle",
ofType: "usdz",
inDirectory: "Asset.scnassets")!
let scene = try! SCNScene(url: URL(string: urlfile)!)
guard let findNode = scene.rootNode.childNode(withName: "armi_idle",
recursively: true)
else {
print("err finde idle")
return
}
// try to pause, but not work
findNode.isPaused = true
// add to main scene
self.scene.rootNode.addChildNode(findNode)
}
A picture of the asset:
Declare the following properties:
import SceneKit
#IBOutlet var sceneView: SCNView!
var notWalking: Bool = true
var animations = [String: CAAnimation]()
var node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene()
self.animation()
}
Then try the following logic (you need Idle and Walking files) :
fileprivate func loadAnimation(withKey: String, scene: String, id: String) {
let url = Bundle.main.url(forResource: scene, withExtension: "dae")!
let source = SCNSceneSource(url: url, options: nil)
guard let character = source?.entryWithIdentifier(id,
withClass: CAAnimation.self) else { return }
character.fadeInDuration = 0.5
character.fadeOutDuration = 0.5
self.animations[withKey] = character
}
then:
fileprivate func animation() {
let standStill = SCNScene(named: "art.scnassets/Idle")!
for childNode in standStill.rootNode.childNodes {
self.node.addChildNode(childNode)
}
sceneView.scene.rootNode.addChildNode(self.node)
self.loadAnimation(withKey: "walking", scene: "art.scnassets/Walking",
id: "Walking-1")
}
and then:
func playWalking(key: String) {
sceneView.scene.rootNode.addAnimation(animations[key]!, forKey: key)
}
func stopWalking(key: String) {
sceneView.scene.rootNode.removeAnimation(forKey: key,
blendOutDuration: 0.75)
}
and at last:
#IBAction func pressed(_ sender: UIButton) {
notWalking ? playWalking(key: "walking") : stopWalking(key: "walking")
notWalking.toggle()
}
In my ThirdScreenViewController I change the variable number with the IBAction pressed.
import Foundation
import UIKit
class ThirdScreenViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
var weatherManager = WeatherManager()
var team = "leer"
static var number = 1
#IBAction func bayernMunchen(_ sender: UIButton) {
team = "bayernMunchen"
}
#IBAction func borussiaDortmund(_ sender: UIButton) {
team = "borussiaDortmund"
}
#IBAction func schalke(_ sender: UIButton) {
team = "schalke"
}
#IBAction func pressed(_ sender: UIButton) {
switch team {
case "bayernMunchen":
ThirdScreenViewController.number = 46
case "borussiaDortmund":
ThirdScreenViewController.number = 41
case "schalke":
ThirdScreenViewController.number = 45
default: print(8)
}
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil)
let nextViewController = storyBoard.instantiateViewController(withIdentifier: "WeatherViewController") as! WeatherViewController
self.present(nextViewController, animated:true, completion:nil)
}
}
In an other swift (not a View Controller) file I have a function which takes number and does something with it.
import Foundation
import UIKit
var TeamOne = ""
var TeamTwo = ""
var ScoreOne = ""
var ScoreTwo = ""
var TeamThree = ""
var TeamFour = ""
var ScoreThree = ""
var ScoreFour = ""
var cityName = ThirdScreenViewController.number
struct WeatherManager {
let weatherURL = "https://livescore-api.com/api-client/teams/matches.json?number=10&team_id=19&key=d33FTnnd6qwvEmjz&secret=BbO3REPYFXvb7fpkit0cQnpXNWssiL1U&number=3&team_id=\(cityName)"
func fetchWeather () {
let urlString = "\(weatherURL)"
perfromRequest(urlString: urlString)
}
func perfromRequest(urlString: String)
{
//1.Url erstellen
if let url = URL(string: urlString) {
//2. URLSession starten
let session = URLSession(configuration: .default)
//3. Give session a task
let task = session.dataTask(with: url) { (gettingInfo, response, error) in
if error != nil{
print(error!)
return
}
if let safeFile = gettingInfo {
self.parseJSON(weatherFile: safeFile)
}
}
//4. Start the task
task.resume()
}
}
//Das Ergebnis von oben wird hier ausgegeben
func parseJSON(weatherFile: Data) {
let decoder = JSONDecoder()
do{
let decodedFile = try decoder.decode(WeatherFile.self, from: weatherFile)
TeamOne = decodedFile.data[0].home_name
ScoreOne = decodedFile.data[0].score
TeamTwo = decodedFile.data[0].away_name
ScoreTwo = decodedFile.data[0].score
TeamThree = decodedFile.data[1].home_name
ScoreThree = decodedFile.data[1].score
TeamFour = decodedFile.data[1].away_name
ScoreFour = decodedFile.data[1].score
} catch {
print(error)
}
}
}
In a third swift file I use this func weatherManager.fetchWeather() to call what happens in my second swift file.
But here is the problem. It takes the variable number with the default value 1 and not with the value 41/46/45. What am I doing wrong?
Basically global variables outside of any class and static variables to share data is bad practice.
Apart from that to get the team ID dynamically delete the line
var cityName = ThirdScreenViewController.number
In the struct replace
let weatherURL = "https://livescore-api.com/api-client/teams/matches.json?number=10&team_id=19&key=d33FTnnd6qwvEmjz&secret=BbO3REPYFXvb7fpkit0cQnpXNWssiL1U&number=3&team_id=\(cityName)"
with
let weatherURL = "https://livescore-api.com/api-client/teams/matches.json?number=10&team_id=19&key=d33FTnnd6qwvEmjz&secret=BbO3REPYFXvb7fpkit0cQnpXNWssiL1U&number=3&team_id="
and
let urlString = "\(weatherURL)"
with
let urlString = weatherURL + String(ThirdScreenViewController.number)
Note: Consider to rename the weather related stuff to the team related stuff
I added in code the MapView, MKUserTrackingButton button, localManager, DispatchGroup:
let mapView: MKMapView = {
let mapView = MKMapView()
mapView.translatesAutoresizingMaskIntoConstraints = false
return mapView
}()
private var userTrackingButton: MKUserTrackingButton!
private let locationManager = CLLocationManager()
let myGroup = DispatchGroup()
var array = [Car]()
In ViewDidLoad I set:
myGroup.enter()
Get data from Firebase:
observeCars()
Wait until I get all the data from Firebase:
myGroup.notify(queue: DispatchQueue.main) {
self.view.addSubview(self.mapView)
//Here code to set the mapView in the view
self.setupUserTrackingButton()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
Here I set my button in the view:
private func setupUserTrackingButton() {
mapView.showsUserLocation = true
userTrackingButton = MKUserTrackingButton(mapView: mapView)
userTrackingButton.layer.backgroundColor = UIColor(white: 0.5, alpha: 1).cgColor
userTrackingButton.layer.borderColor = UIColor.white.cgColor
userTrackingButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(userTrackingButton)
///Code to set in the view the button
}
Class where I set connection to Firebase to get data:
class APIService: NSObject {
class func observeCars(completion: ((_ car: Car) -> Void)?) {
let ref = Database.database().reference()
ref.child("Cars").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
let car = Car(dictionary: dictionary)
completion?(car)
}
}, withCancel: nil)
}
}
This is the function (written in the MainViewController) where I get the data from Firebase and add it to the array:
internal func observeCars() {
/////I think this is the part that makes It doesn't appear because
if I delete this part, it works like I aspect
APIService.observeCars {
(car) in
self.array.append(car)
print(self.array.count)
if self.array.count == totalCars {
self.myGroup.leave()
}
}
//////////
}
Any hints? Thanks
I added the MKUserTrackingButton in the mapView:
mapView.addSubview(userLocationButton)
It works to me.
I want to detect object and track that object using vision framework. I am successfully done with detect objects and little bit with tracking also but I don't get so much accuracy with tracking.
I want much more accuracy while converting frames as its frequently lost the accuracy while track the objects.
Please check the below code for detect and track the objects:
import UIKit
import AVFoundation
import Vision
class ViewController: UIViewController {
private lazy var captureSession: AVCaptureSession = {
let session = AVCaptureSession()
session.sessionPreset = AVCaptureSession.Preset.photo
guard let backCamera = AVCaptureDevice.default(for: .video),
let input = try? AVCaptureDeviceInput(device: backCamera) else
{
return session
}
session.addInput(input)
return session
}()
private lazy var cameraLayer: AVCaptureVideoPreviewLayer =
AVCaptureVideoPreviewLayer(session: self.captureSession)
private let handler = VNSequenceRequestHandler()
fileprivate var lastObservation: VNDetectedObjectObservation?
lazy var highlightView: UIView = {
let view = UIView()
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 4
view.backgroundColor = .clear
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(cameraLayer)
view.addSubview(highlightView)
let output = AVCaptureVideoDataOutput()
output.setSampleBufferDelegate(self, queue: DispatchQueue(label:
"queue"))
captureSession.addOutput(output)
captureSession.startRunning()
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: #selector(tapAction))
view.addGestureRecognizer(tapGestureRecognizer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
cameraLayer.frame = view.bounds
}
// MARK: - Actions
#objc private func tapAction(recognizer: UITapGestureRecognizer) {
highlightView.frame.size = CGSize(width: 120, height: 120)
highlightView.center = recognizer.location(in: view)
let originalRect = highlightView.frame
var convertedRect =
cameraLayer.metadataOutputRectConverted(fromLayerRect:
originalRect)
convertedRect.origin.y = 1 - convertedRect.origin.y
lastObservation = VNDetectedObjectObservation(boundingBox:
convertedRect)
}
fileprivate func handle(_ request: VNRequest, error: Error?) {
DispatchQueue.main.async {
guard let newObservation = request.results?.first as?
VNDetectedObjectObservation else {
return
}
self.lastObservation = newObservation
var transformedRect = newObservation.boundingBox
transformedRect.origin.y = 1 - transformedRect.origin.y
let convertedRect =
self.cameraLayer.layerRectConverted(fromMetadataOutputRect:
transformedRect)
self.highlightView.frame = convertedRect
}
}
}
extension ViewController:
AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer:
CMSampleBuffer, from connection: AVCaptureConnection) {
guard let pixelBuffer =
CMSampleBufferGetImageBuffer(sampleBuffer),
let observation = lastObservation else {
return
}
let request = VNTrackObjectRequest(detectedObjectObservation:
observation) { [unowned self] request, error in
self.handle(request, error: error)
}
request.trackingLevel = .accurate
do {
try handler.perform([request], on: pixelBuffer)
}
catch {
print(error)
}
}
}
Any help will be appreciated!!
Thanks.
I am not so good at vision and core ml, but apparently your code looks fine. One thing you can do is check when vision does not get any tracking in the buffer, you have to mark its property isLastFrame true if tracking request confidence value falls to 0.
if !trackingRequest.isLastFrame {
if observation.confidence > 0.7 {
trackingRequest.inputObservation = observation
} else {
trackingRequest.isLastFrame = true
}
newTrackingRequests.append(trackingRequest)
}
This way its easy to find out whether vision tracking request lost tracking object or it just tracking the wrong object.
I am having a problem decoding an object after I encode it in Swift.
Here is my class:
class Player: NSObject, NSCoding {
var score:Int = 0
init(difficulty: Int!) {
super.init()
}
required init(coder aDecoder: NSCoder) {
score = aDecoder.decodeObjectForKey("score") as Int
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(score, forKey: "score")
}
Here is when I am encoding and decoding:
let data = NSKeyedArchiver.archivedDataWithRootObject(player)
let newPlayer = NSKeyedUnarchiver.unarchiveObjectWithData(data) as Player
It crashes everytime when I try to unarchive it. Anybody have any reasons why?
P.S. I have no idea why my code isn't formatted. I have it indented 4 spaces over! Sorry about that!
Here is what I did to get it working:
Created an instance of player in my App Delegate
You can call this anywhere if your application UIApplication.sharedApplication().player
For storing arrays of Player objects here are the NSObject classes:
import Foundation
class Player: NSObject {
var score:Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(score, forKey: "score")
}
init(coder aDecoder: NSCoder!) {
score = aDecoder.decodeIntegerForKey("score")
}
override init() {
}
}
class ArchivePlayer:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func savePlayerArray(#playerArray: [Player]) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("playersArray.archive")
if NSKeyedArchiver.archiveRootObject(playerArray, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func retrievePlayerArray() -> NSObject {
var dataToRetrieve = [Player]()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("playersArray.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? [Player] {
dataToRetrieve = dataToRetrieve2
}
return(dataToRetrieve)
}
}
Here is the ViewController:
import UIKit
class ViewController: UIViewController {
var player1 = Player()
var player2 = Player()
var player3 = Player()
var playerArchiveArray = [Player]()
override func viewDidLoad() {
super.viewDidLoad()
player1.score = 12
player2.score = 22
player3.score = 32
playerArchiveArray = [player1, player2, player3]
ArchivePlayer().savePlayerArray(playerArray: playerArchiveArray)
var playerRetrieveArray = ArchivePlayer().retrievePlayerArray() as [Player]
for player in playerRetrieveArray {
println(player.score)
}
}
}
This should work. I will post a separate answer if you wish to save an array of Player objects.
Two NSObject classes:
import Foundation
class Player: NSObject {
var score:Int = 0
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeInteger(score, forKey: "score")
}
init(coder aDecoder: NSCoder!) {
score = aDecoder.decodeIntegerForKey("score")
}
override init() {
}
}
class ArchivePlayer:NSObject {
var documentDirectories:NSArray = []
var documentDirectory:String = ""
var path:String = ""
func savePlayer(#player: Player) {
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("players.archive")
if NSKeyedArchiver.archiveRootObject(player, toFile: path) {
println("Success writing to file!")
} else {
println("Unable to write to file!")
}
}
func retrievePlayer() -> NSObject {
var dataToRetrieve = Player()
documentDirectories = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
documentDirectory = documentDirectories.objectAtIndex(0) as String
path = documentDirectory.stringByAppendingPathComponent("players.archive")
if let dataToRetrieve2 = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? Player {
dataToRetrieve = dataToRetrieve2 as Player
}
return(dataToRetrieve)
}
}
And then in the ViewController:
import UIKit
class ViewController: UIViewController {
var player = Player()
override func viewDidLoad() {
super.viewDidLoad()
player.score = 22
ArchivePlayer().savePlayer(player: player)
let playerToRetrieve = ArchivePlayer().retrievePlayer() as Player
println(playerToRetrieve.score)
}
}
This demonstrates archiving and unarchiving to file. Printing "Success writing to file!" to demonstrate the archiving. Printing the stored object player.score to demonstrate unarchiving.