Get size of dae node - scenekit

Suppose I have a collada file which has a box in it and I import the dae file into the scene. Now after importing in the scene I know the dae object is a box. How can I get the dimensions of the box in scenekit after adding it to the scene
If I import the node as a SCNBox i get errors saying that SNCBox is not a subtype of SCNNode.
floorNode = scene.rootNode.childNodeWithName("floor", recursively: false) as SCNBox
floorNode?.physicsBody = SCNPhysicsBody.staticBody()
floorNode?.physicsBody?.categoryBitMask = 2
let floorGeo: SCNBox = floorNode.geometry! as SCNBox
How do I get the dimensions if SCNNode is the only way to import nodes?

SCNBox is just helper subclass of SCNGeometry for box geometry creation. When you import Collada into a scene, you get scene graph of SCNNodes with SCNGeometries, not SCNBox'es or SCNCones etc doesn't matter how they look. If you want to get dimensions of any node you should use SCNBoundingVolume protocol, which is implemented by both SCNNode and SCNGeometry classes:
func getBoundingBoxMin(_ min: UnsafeMutablePointer,
max max: UnsafeMutablePointer) -> Bool
With this method you will get bounding box corners.
For box-shaped geometry, dimensions will match bounding box.
Example:
var v1 = SCNVector3(x:0, y:0, z:0)
var v2 = SCNVector3(x:0, y:0, z:0)
node.getBoundingBoxMin(&v1, max:&v2)
Where node is node you want to get bounding box of.
Results will be in v1 and v2.
Swift 3
Using Swift 3 you can simply use node.boundingBox.min and node.boundingBox.max respectively.

Example Swift code on how to use boundingBox:
var min = shipNode.boundingBox.min
var max = shipNode.boundingBox.max
let w = CGFloat(max.x - min.x)
let h = CGFloat(max.y - min.y)
let l = CGFloat(max.z - min.z)
let boxShape = SCNBox (width: w , height: h , length: l, chamferRadius: 0.0)
let shape = SCNPhysicsShape(geometry: boxShape, options: nil)
shipNode.physicsBody!.physicsShape = SCNPhysicsShape(geometry: boxShape, options: nil)
shipNode.physicsBody = SCNPhysicsBody.dynamic()

Swift 5.4.
import ARKit
extension SCNNode {
var height: CGFloat { CGFloat(self.boundingBox.max.y - self.boundingBox.min.y) }
var width: CGFloat { CGFloat(self.boundingBox.max.x - self.boundingBox.min.x) }
var length: CGFloat { CGFloat(self.boundingBox.max.z - self.boundingBox.min.z) }
var halfCGHeight: CGFloat { height / 2.0 }
var halfHeight: Float { Float(height / 2.0) }
var halfScaledHeight: Float { halfHeight * self.scale.y }
}
// Usage:
let node = SCNNode()
node.height
node.width
node.lenght

Related

SwiftUI + Scenekit + Combine + GeometryReader ==> Scrollable List of 3D Models + Rotation of 3D Models By Scrolling the List

I want to create a scrollable list of 3D models(here: default ship model of Xcode which you can find in a Game project of Xcode).
For that I've created 3 View structures:
one for viewing the 3D model (called ScenekitView)
one for a CardView (called MyCardView)
one for the scrollable list of 3D models(called MyCardsListView)
Here are the Structures:
ScenekitView
(btw I added the art.scnassets folder of Xcode's default Game project to my Single View App project)
import SwiftUI
import SceneKit
struct ScenekitView : UIViewRepresentable {
#Binding var yRotation : Float
let scene = SCNScene(named: "art.scnassets/ship.scn")!
func makeUIView(context: Context) -> SCNView {
// create and add a camera to the scene
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
// place the camera
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// create and add a light to the scene
let lightNode = SCNNode()
lightNode.light = SCNLight()
lightNode.light!.type = .omni
lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
scene.rootNode.addChildNode(lightNode)
// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
// retrieve the ship node
let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!
// animate the 3d object
ship.eulerAngles = SCNVector3(x: 0, y: self.yRotation * Float((Double.pi)/180.0) , z: 0)
// retrieve the SCNView
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = true
// configure the view
scnView.backgroundColor = UIColor.black
}
}
#if DEBUG
struct ScenekitView_Previews : PreviewProvider {
static var previews: some View {
ScenekitView(yRotation: .constant(0))
}
}
#endif
MyCardView
import SwiftUI
struct MyCardView : View {
#Binding var yRotation: Float
var body: some View {
VStack(spacing: 0) {
ZStack {
Rectangle()
.foregroundColor(.black)
.opacity(0.8)
.frame(width:250, height: 70)
Text( "Ship \( self.yRotation )" )
.foregroundColor(.white)
.font(.title)
.fontWeight(.bold)
}
ScenekitView(yRotation: self.$yRotation )
.frame(width: 250, height: 250)
}
.cornerRadius(35)
.frame(width: 250,height: 320)
}
}
#if DEBUG
struct MyCardView_Previews : PreviewProvider {
static var previews: some View {
MyCardView(yRotation: .constant(90))
}
}
#endif
MyCardsListView
import SwiftUI
struct MyCardsListView: View {
#State var yRotation: Float = 0
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(0...2) { number in
GeometryReader { geometry in
MyCardView(yRotation: self.$yRotation )
.rotation3DEffect(Angle(degrees: Double(geometry.frame(in: .global).minX) / -20), axis: (x: 0, y: 10, z: 0))
}
.padding()
.frame(width: 250, height: 350)
}
}
}
}
}
#if DEBUG
struct Product3DView_Previews: PreviewProvider {
static var previews: some View {
MyCardsListView()
}
}
#endif
In MyCardsListView I'm getting each Card's minimum X position using GeometryReader
My goal is:
while I'm scrolling the Cards in MyCardsListView, every ship model should rotate with respect to its Card's minimum X position (all of them shouldn't rotate with same rotation angle at the same time)
(I know I might made lots of mistakes in using Bindings, States and etc, Sorry about that)
There are many problems in your code:
You never assign a value to yRotation.
You only have one yRotation variable in MyCardsListView, but because you want to have different rotation values, depending on the view's position, you need one yRotation variable for each view.
The following line:
ship.eulerAngles = SCNVector3(x: 0, y: self.yRotation * Float((Double.pi)/180.0) , z: 0)
is inside the makeUIView() method, but that method is only executed once (when your view is created). You want the value to update continously, so you should assign the value in updateUIView() instead.
You should definitely watch WWDC sessions 226: Data Flow in SwiftUI and 231: Integrating SwiftUI.

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)

Splitting an image into a grid in Swift

I am attempting to split an image into 10'000 smaller ones, by dividing the image into a grid of 100x100. When I run the code, I receive an Error_Bad_Instruction.
import Cocoa
import CoreImage
public class Image {
public var image: CGImage?
public func divide() -> [CGImage] {
var tiles: [CGImage] = []
for x in 0...100 {
for y in 0...100 {
let tile = image?.cropping(to: CGRect(x: (image?.width)!/x, y: (image?.height)!/y, width: (image?.width)! / 100, height: (image?.height)! / 100 ))
tiles.append(tile!)
}
}
return tiles
}
public init(image: CGImage) {
self.image = image
}
}
var img = Image(image: (NSImage(named: "aus-regions.jpg")?.cgImage(forProposedRect: nil, context: nil, hints: nil))!)
var a = img.divide()
There are some problems with the code that you posted:
forced wrapping the image
loops go including 100 - you just need 99. Or as a comment suggests: for x in 0..<100
you divide by 0 - in the first step
your algorithm, is not forming a grid.
I've updated to obtain a grid, using an image that I had on my computer, and with widths and heights just of 10, because it using 100 was too long.
import Cocoa
import CoreImage
let times = 10
public class Image {
public var image: CGImage?
public func divide() -> [CGImage] {
var tiles: [CGImage] = []
for x in 0..<times {
for y in 0..<times {
let tile = image?.cropping(to: CGRect(x: x * (image?.width)!/times, y: y * (image?.height)!/times, width: (image?.width)! / times, height: (image?.height)! / times ))
tiles.append(tile!)
}
}
return tiles
}
public init(image: CGImage) {
self.image = image
}
}
if let image = NSImage(named: "bg.jpg")?.cgImage(forProposedRect: nil, context: nil, hints: nil) {
var img = Image(image: image)
var a = img.divide()
}

How to create a copy of UIView objects, and decrease the font size of a particular label, where views are made programmatically, in Swift?

I have created views programmatically on sub views.
let view1 = UIView(frame: CGRectMake(30, cgfloat , 270, 120))
view1.backgroundColor = UIColor.whiteColor()
view1.layer.cornerRadius = 40.0
let Attdate: UILabel = UILabel()
Attdate.frame = CGRectMake(15, 10, 100, 25)
Attdate.backgroundColor = UIColor.orangeColor()
Attdate.textColor = UIColor.blackColor()
Attdate.font = UIFont(name: "BebasNeue", size: 106)
Attdate.textAlignment = NSTextAlignment.Left
Attdate.text = AttDate.description
view1.addSubview(Attdate)
I have an array as a response string from server which gives me an array of data. And I want to print that data into the labels. The labels should get called dynamically as the per the array length. And thats THE reason, I am trying to duplicate view1(my UIView object). I tried nskeyedarchiver(not sure how it will help).
extension UIView{
{
func copyView() -> AnyObject
{
return NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(self))!
}
}
And declared:
let view1 = UIView()
let copiedView = view1.copyView() as! UIView
print("CopiedView:\(copiedView)")
But, no luck :(
Also, I tried many syntaxes to decrease the font size of a particular label, but none seemed to work.
Kindly reply.
To decrease the font size of a particular label, there are two options:
You can make the font size part of an initializer, so you can set it every time you have to create one, like this:
.
class CustomView: UIView {
init(fontSizeOfLabel : CGFloat) {
super.init(frame: CGRect(x: 30, y: 20, width: 270, height: 120))
// all your normal code in here
let Attdate: UILabel = UILabel()
Attdate.font = UIFont(name: "BebasNeue", size: fontSizeOfLabel)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Or you can loop over its subviews and change it after you have created it:
.
var instance = CustomView()
for subview in instance.subviews {
if var label = subview as? UILabel { //check if it can convert the subview into a UILabel, if it can it is your label
label.font = UIFont(name: "BebasNeue", size: 70)
}
}
I haven't tested the second solution, I have checked the first one

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