SCNVector4: Rotate on x-axis - swift

I have a sphere and managed to rotate it. But unfortunately along the wrong axis.
My goal is a rotation like the earth along the x axis. Can you help me to apply this?
Here is my existing code:
let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: Float(CGFloat(-2 * Double.pi))))
spin.duration = 30
spin.repeatCount = .infinity
sphereNode.addAnimation(spin, forKey: "spin around")

It will be something like this
self.imageView.layer.transform = CATransform3DConcat(self.imageView.layer.transform, CATransform3DMakeRotation(M_PI,1.0,0.0,0.0));

You're already have working solution:
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
If you wanna change direction, just change vectors :)
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: CGFloat.pi * 2))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 1, y: 0, z: 0, w: 0))

You can rotate your Earth model about X-axis using SceneKit's Transaction:
let scene = SCNScene(named: "art.scnassets/model.scn")!
let modelEarth = scene.rootNode.childNode(withName: "model",
recursively: true)!
SCNTransaction.begin()
SCNTransaction.animationDuration = 500 // animation in seconds
SCNTransaction.animationTimingFunction = .init(name: .default)
modelEarth.rotation.x = 1
modelEarth.rotation.y = 0
modelEarth.rotation.z = 0
modelEarth.rotation.w = 100 * Float.pi // fourth component
SCNTransaction.commit()

Related

SCNNode not moving due to animation

I created SCNNode object and added simple CABasicAnimation that just spins my object. I used to set velocity to the physics body of my node, but it’s not moving while animation is playing. However other animations which were created in blender are working.
Example:
self.node = SCNNode()
let obj = SCNScene(named: "art.scnassets/player.scn")!
self.node = (obj.rootNode.childNode(withName: "Body", recursively: true))!
let spin = CABasicAnimation(keyPath: "rotation")
spin.fromValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: 0))
spin.toValue = NSValue(scnVector4: SCNVector4(x: 0, y: 1, z: 0, w: Float(CGFloat(2 * Double.pi))))
spin.duration = 1
spin.repeatCount = .infinity
self.node.addAnimation(SCNAnimation(caAnimation: spin), forKey: "spin")
scene.rootNode.addChildNode(self.node)
self.player.node.physicsBody?.velocity.x = 2.0 // not working together with animation

swift SceneKit Sphere deformation after changing position

I have two spheres on the sceneView
let earthSphere = SCNSphere(radius: 5.0)
earthSphere.firstMaterial?.diffuse.contents = UIColor.green
let objNode1 = SCNNode(geometry: earthSphere)
objNode1.position = SCNVector3(x: 0, y: 0, z: 0)
let moonSphere = SCNSphere(radius: 0.5)
moonSphere.firstMaterial?.diffuse.contents = UIColor.red
let objNode2 = SCNNode(geometry: moonSphere)
objNode2.position = SCNVector3(x: 3, y: 3, z: 0)
but moonSphere is deforming image. How to fix that ? It is possible to do that earthSphere and moonSphere look same ? without deformation
etc: cameraNode set to position SCNVector3(x: 0, y: 0, z: 7)

Creating a custom SCNGeometry polygon plane with SCNGeometryPrimitiveType polygon crash/error

I'm trying to create a custom SCNGeometry in the form of a plane with custom shape, which could be placed in an ARKit session. I'm using the option SCNGeometryPrimitiveTypePolygon in the following method which seems to work fine:
extension SCNGeometry {
static func polygonPlane(vertices: [SCNVector3]) -> SCNGeometry {
var indices: [Int32] = [Int32(vertices.count)]
var index: Int32 = 0
for _ in vertices {
indices.append(index)
index += 1
}
let vertexSource = SCNGeometrySource(vertices: vertices)
let indexData = Data(bytes: indices, count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: indexData, primitiveType: .polygon, primitiveCount: 1, bytesPerIndex: MemoryLayout<Int32>.size)
let geometry = SCNGeometry(sources: [vertexSource], elements: [element])
let material = SCNMaterial()
material.diffuse.contents = UIColor.blue
material.isDoubleSided = true
geometry.firstMaterial = material
return geometry
}
After creating this geometry I assign it to a SCNNode:s .geometry property and add it to my AR scene as usual:
let geometry = SCNGeometry.polygonPlane(vertices: verticesArray)
let node = SCNNode(geometry: geometry)
sceneView.scene.rootNode.addChildNode(node)
This works well for some types of plane shapes. However I often get a crash, mostly when using complex shapes or many vertices to outline the plane shape. I've experimented and it seems as the geometry is created as expected without any errors, but the error occurs when the node is added to the scene and about to be rendered. This is the printed error:
-[MTLDebugDevice validateNewBufferArgs:options:]:467: failed assertion `Cannot create buffer of zero length.'
What appears in the debug navigator:
And finally the stack trace:
* thread #17, name = 'com.apple.scenekit.scnview-renderer', queue = 'com.apple.scenekit.renderingQueue.ARSCNView0x11be03ed0', stop reason = signal SIGABRT
* frame #0: 0x0000000219cad0cc libsystem_kernel.dylib`__pthread_kill + 8
frame #1: 0x0000000219d23a88 libsystem_pthread.dylib`pthread_kill + 300
frame #2: 0x0000000219c0614c libsystem_c.dylib`abort + 144
frame #3: 0x0000000219bd3274 libsystem_c.dylib`__assert_rtn + 224
frame #4: 0x000000021c28e23c Metal`MTLReportFailure + 528
frame #5: 0x000000023f984108 MetalTools`-[MTLDebugDevice validateNewBufferArgs:options:] + 172
frame #6: 0x000000023f98430c MetalTools`-[MTLDebugDevice newBufferWithBytes:length:options:] + 128
frame #7: 0x000000022e323b48 SceneKit`-[SCNMTLResourceManager _bufferForData:bytesPerIndex:] + 404
frame #8: 0x000000022e323f14 SceneKit`-[SCNMTLResourceManager renderResourceForMeshElement:] + 416
frame #9: 0x000000022e3243c0 SceneKit`-[SCNMTLResourceManager renderResourceForMesh:dataKind:] + 692
frame #10: 0x000000022e364980 SceneKit`_execute(SCNMTLRenderContext*, DrawCommand) + 916
frame #11: 0x000000022e3644a0 SceneKit`-[SCNMTLRenderContext drawRenderElement:withPass:] + 608
frame #12: 0x000000022e3630d4 SceneKit`-[SCNMTLRenderContext processRendererElements:count:engineIterationContext:] + 1044
frame #13: 0x000000022e4afc54 SceneKit`C3D::DrawNodesPass::_renderEye(long) + 472
frame #14: 0x000000022e4afa00 SceneKit`C3D::DrawNodesPass::execute(C3D::RenderArgs const&) + 260
frame #15: 0x000000022e54a1b4 SceneKit`C3D::MainPass::execute(C3D::RenderArgs const&) + 176
frame #16: 0x000000022e318904 SceneKit`C3D::__renderSlice(C3D::RenderGraph*, C3D::RenderPass*, unsigned short&, C3D::RenderGraph::GraphNode const&, C3D::RenderGraph::Stage*&, C3D::RenderArgs) + 1156
frame #17: 0x000000022e319cd0 SceneKit`C3D::RenderGraph::execute() + 3896
frame #18: 0x000000022e42fd8c SceneKit`-[SCNRenderer _renderSceneWithEngineContext:sceneTime:] + 2364
frame #19: 0x000000022e42ff44 SceneKit`-[SCNRenderer _drawSceneWithNewRenderer:] + 312
frame #20: 0x000000022e43056c SceneKit`-[SCNRenderer _drawScene:] + 72
frame #21: 0x000000022e4309bc SceneKit`-[SCNRenderer _drawAtTime:] + 760
frame #22: 0x000000022e4dfecc SceneKit`-[SCNView _drawAtTime:] + 492
frame #23: 0x000000022e37c15c SceneKit`__69-[NSObject(SCN_DisplayLinkExtensions) SCN_setupDisplayLinkWithQueue:]_block_invoke + 60
frame #24: 0x000000022e4a1c50 SceneKit`__36-[SCNDisplayLink _callbackWithTime:]_block_invoke + 88
frame #25: 0x0000000104d74778 libdispatch.dylib`_dispatch_client_callout + 20
frame #26: 0x0000000104d82fc0 libdispatch.dylib`_dispatch_lane_barrier_sync_invoke_and_complete + 160
frame #27: 0x000000022e4a1bb8 SceneKit`-[SCNDisplayLink _callbackWithTime:] + 268
frame #28: 0x0000000104e082d4 GPUToolsCore`-[DYDisplayLinkInterposer forwardDisplayLinkCallback:] + 204
frame #29: 0x000000021e53aea8 QuartzCore`CA::Display::DisplayLink::dispatch_items(unsigned long long, unsigned long long, unsigned long long) + 632
frame #30: 0x000000021e608858 QuartzCore`display_timer_callback(__CFMachPort*, void*, long, void*) + 276
frame #31: 0x000000021a083058 CoreFoundation`__CFMachPortPerform + 192
frame #32: 0x000000021a0aaaf0 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 60
frame #33: 0x000000021a0aa1e8 CoreFoundation`__CFRunLoopDoSource1 + 444
frame #34: 0x000000021a0a4d80 CoreFoundation`__CFRunLoopRun + 2060
frame #35: 0x000000021a0a4254 CoreFoundation`CFRunLoopRunSpecific + 452
frame #36: 0x000000021aa8404c Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 304
frame #37: 0x000000022e37c518 SceneKit`__49-[SCNView(SCNDisplayLink) _initializeDisplayLink]_block_invoke + 444
frame #38: 0x000000022e37c684 SceneKit`__SCNRenderThread_start__ + 104
frame #39: 0x0000000219d22908 libsystem_pthread.dylib`_pthread_body + 132
frame #40: 0x0000000219d22864 libsystem_pthread.dylib`_pthread_start + 48
frame #41: 0x0000000219d2adcc libsystem_pthread.dylib`thread_start + 4
warning: failed to set breakpoint site at 0x21931425c for breakpoint -4.1: error sending the breakpoint request
warning: failed to set breakpoint site at 0x219314530 for breakpoint -4.2: error sending the breakpoint request
warning: failed to set breakpoint site at 0x2193141b4 for breakpoint -4.3: error sending the breakpoint request
warning: failed to set breakpoint site at 0x21931fcc4 for breakpoint -5.1: error sending the breakpoint request
I'm not experienced in debugging in swift but it seems to me the problem occurs somewhere in frame #4-6 within some metal-framework code:
frame #4: 0x000000021c28e23c Metal`MTLReportFailure + 528
frame #5: 0x000000023f984108 MetalTools`-[MTLDebugDevice validateNewBufferArgs:options:] + 172
frame #6: 0x000000023f98430c MetalTools`-[MTLDebugDevice newBufferWithBytes:length:options:] + 128
Thankful for any help/suggestions!
---- EDIT 1 ----
Here is an array which does not generate this error:
vertices = [ //Array that should work
SCNVector3(x: -0.06110339, y: -0.00659544, z: -0.18046863),
SCNVector3(x: -0.06406027, y: -0.008907169, z: -0.18053372),
SCNVector3(x: -0.06406027, y: -0.008907169, z: -0.18053372),
SCNVector3(x: -0.06701318, y: -0.013257578, z: -0.18059872),
SCNVector3(x: -0.069427274, y: -0.017816536, z: -0.18065183),
SCNVector3(x: -0.07077661, y: -0.02299612, z: -0.18068156),
SCNVector3(x: -0.07138735, y: -0.029295363, z: -0.18069498),
SCNVector3(x: -0.07159121, y: -0.035330035, z: -0.1806995),
SCNVector3(x: -0.06850778, y: -0.039139934, z: -0.1806316),
SCNVector3(x: -0.059540674, y: -0.039537176, z: -0.18043421),
SCNVector3(x: -0.04808737, y: -0.035914123, z: -0.1801821),
SCNVector3(x: -0.045074403, y: -0.035180397, z: -0.1801158)
]
Here are two screenshots from my app when using this array. As you can see the polygon lies within the plane since all vertex positions are picked from a hit test on that plane. Don't mind the clipping colors since it's due to z-fighting and can easily be fixed by offsetting the planes:
Here is an example of a vertices array which generates this error:
vertices = [ //Array that should not work
SCNVector3(x: 0.08866002, y: -0.007735528, z: -0.09841499),
SCNVector3(x: 0.08873053, y: -0.014926873, z: -0.09837532),
SCNVector3(x: 0.08873053, y: -0.014926873, z: -0.09837532),
SCNVector3(x: 0.08846086, y: -0.024348512, z: -0.09852711),
SCNVector3(x: 0.08749959, y: -0.034751557, z: -0.09906833),
SCNVector3(x: 0.08527064, y: -0.043312013, z: -0.10032329),
SCNVector3(x: 0.08125973, y: -0.049623042, z: -0.10258152),
SCNVector3(x: 0.07674095, y: -0.054563493, z: -0.10512567),
SCNVector3(x: 0.07041831, y: -0.057908192, z: -0.10868551),
SCNVector3(x: 0.06373097, y: -0.058204524, z: -0.112450644),
SCNVector3(x: 0.058445737, y: -0.057790123, z: -0.115426354),
SCNVector3(x: 0.054485526, y: -0.05334358, z: -0.11765605),
SCNVector3(x: 0.052902386, y: -0.04610482, z: -0.1185474),
SCNVector3(x: 0.053534307, y: -0.036374755, z: -0.118191615),
SCNVector3(x: 0.055890974, y: -0.027881026, z: -0.11686475),
SCNVector3(x: 0.059101492, y: -0.022751786, z: -0.115057185),
SCNVector3(x: 0.062345386, y: -0.02150976, z: -0.113230795),
SCNVector3(x: 0.06506948, y: -0.022176817, z: -0.11169703)
]
And here is a screenshot from when using that array. The blue planes are instances when the method worked, but if you look closely there is a red line. This red line is following the vertices of the bad array above, which crashes the app instantly when the polygon plane using the corresponding geometry is added to the scene:
---- EDIT 2 ----
Below is the code for #ARGeo's macOS solution, but slightly modified to fit my iOS app and to use an arbitrary amount of vertices. The code works for the vertices array that previously failed but fails again when using another vertices array. Like #ARGeo proposed I made sure to place this method inside of the actual class and not in an extension in case MemoryLayout<Int32>.size is causing the issue when used inside an extension.
private func createPolygon(){
func model(vertices: [SCNVector3]) -> SCNNode {
let polyDraw = draw(vertices: vertices)
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
material.isDoubleSided = true
material.diffuse.contentsTransform = .init(m11: 0.1, m12: 0, m13: 0, m14: 0,
m21: 0, m22: 0.1, m23: 0, m24: 0,
m31: 0, m32: 0, m33: 0, m34: 0,
m41: 0, m42: 0, m43: 0, m44: 1)
material.diffuse.wrapS = .repeat
material.diffuse.wrapT = .repeat
polyDraw.materials = [material]
let node = SCNNode(geometry: polyDraw)
//node.scale = SCNVector3(x: 200, y: 200, z: 200)
sceneView.scene.rootNode.addChildNode(node)
return node
}
func draw(vertices: [SCNVector3]) -> SCNGeometry {
let normalsPerFace = 1
var indices: [Int32] = []
indices.append(Int32(vertices.count))
//Add the rest of the indices 0 to vertices.count-1
for i in 0 ... vertices.count-1 {
indices.append(Int32(i))
}
let source = SCNGeometrySource(vertices: vertices)
let vec = vertices.map { [SCNVector3](repeating: $0,
count: normalsPerFace) }.flatMap{ $0 }
let normals: [SCNVector3] = vec
let normalSource = SCNGeometrySource(normals: normals)
var cgps: [CGPoint] = []
vertices.forEach { (vertex) in
cgps.append(CGPoint(x: CGFloat(vertex.x), y: CGFloat(vertex.y)))
}
let textcoord = SCNGeometrySource(textureCoordinates: cgps)
let data = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: data,
primitiveType: .polygon,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [source, normalSource, textcoord], elements: [element])
}
//Previous fail, now success with ARGeo's code
let vertices = [SCNVector3(x: 0.08866002, y: -0.00773552, z: -0.09841499),
SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
SCNVector3(x: 0.08846086, y: -0.02434851, z: -0.09852711),
SCNVector3(x: 0.08749959, y: -0.03475155, z: -0.09906833),
SCNVector3(x: 0.08527064, y: -0.04331201, z: -0.10032329),
SCNVector3(x: 0.08125973, y: -0.04962304, z: -0.10258152),
SCNVector3(x: 0.07674095, y: -0.05456349, z: -0.10512567),
SCNVector3(x: 0.07041831, y: -0.05790819, z: -0.10868551),
SCNVector3(x: 0.06373097, y: -0.05820452, z: -0.11245064),
SCNVector3(x: 0.05844573, y: -0.05779012, z: -0.11542635),
SCNVector3(x: 0.05448552, y: -0.05334358, z: -0.11765605),
SCNVector3(x: 0.05290238, y: -0.04610482, z: -0.11854740),
SCNVector3(x: 0.05353430, y: -0.03637475, z: -0.11819161),
SCNVector3(x: 0.05589097, y: -0.02788102, z: -0.11686475),
SCNVector3(x: 0.05910149, y: -0.02275178, z: -0.11505718),
SCNVector3(x: 0.06234538, y: -0.02150976, z: -0.11323079),
SCNVector3(x: 0.06506948, y: -0.02217681, z: -0.11169703)
]
_ = model(vertices: vertices)
}
Example of vertices array for which the code above fails:
vertices = [ SCNVector3(x: 0.08291423, y: -0.08406013, z: -0.60201955),
SCNVector3(x: 0.077855, y: -0.083336316, z: -0.60234916),
SCNVector3(x: 0.077855, y: -0.083336316, z: -0.60234916),
SCNVector3(x: 0.06817482, y: -0.08799789, z: -0.6029798),
SCNVector3(x: 0.055873748, y: -0.09737456, z: -0.6037812),
SCNVector3(x: 0.042388167, y: -0.108595274, z: -0.60465986),
SCNVector3(x: 0.031522393, y: -0.119523935, z: -0.6053677),
SCNVector3(x: 0.024102041, y: -0.13026507, z: -0.6058511),
SCNVector3(x: 0.021609604, y: -0.13820335, z: -0.6060136),
SCNVector3(x: 0.022751667, y: -0.14294992, z: -0.60593915),
SCNVector3(x: 0.025871918, y: -0.14491153, z: -0.6057359),
SCNVector3(x: 0.0338943, y: -0.14688163, z: -0.6052132),
SCNVector3(x: 0.041132875, y: -0.15027393, z: -0.60474163),
SCNVector3(x: 0.047307685, y: -0.15410759, z: -0.6043393),
SCNVector3(x: 0.054541387, y: -0.1566292, z: -0.603868),
SCNVector3(x: 0.06140149, y: -0.15919833, z: -0.60342115),
SCNVector3(x: 0.06551884, y: -0.16264887, z: -0.6031529)
]
---- EDIT 3 ----
Okay so I made a macOS version for the code above (which is the solution #ARGeo proposed but with some minor changes, like using an arbitrary amount of vertices) which does not crash when using the above array, even though the iOS code version does. However nothing is rendered and I'm not getting any error messages which I don't know the reason for. When adding the line cameraNode.camera?.zFar = 1000 in case It's not visible due to clipping, the program crashes instead, giving a new error. It does not crash if zFar is small enough (tested with zFar < 10), in which case the polygon is not rendered either. The error print is the same -[MTLDebugDevice validateNewBufferArgs:options:]:467: failed assertion 'Cannot create buffer of zero length.' but the debug navigator shows this:
and the stack trace's final rows are this:
* thread #8, name = 'CVDisplayLink', queue = 'com.apple.scenekit.renderingQueue.SCNView0x101306480', stop reason = signal SIGABRT
* frame #0: 0x00007fff671b62c6 libsystem_kernel.dylib`__pthread_kill + 10
frame #1: 0x000000010053680d libsystem_pthread.dylib`pthread_kill + 284
frame #2: 0x00007fff671206a6 libsystem_c.dylib`abort + 127
frame #3: 0x00007fff670e920d libsystem_c.dylib`__assert_rtn + 324
frame #4: 0x00007fff3fd1b68e Metal`MTLReportFailure + 567
frame #5: 0x00007fff599f9d98 MetalTools`-[MTLDebugDevice validateNewBufferArgs:options:] + 207
frame #6: 0x00007fff599f9f28 MetalTools`-[MTLDebugDevice newBufferWithBytes:length:options:] + 107
frame #7: 0x00007fff462f265b SceneKit`-[SCNMTLResourceManager _bufferForData:bytesPerIndex:] + 414
frame #8: 0x00007fff462f29e0
Here is the code which is used if you want to try it out yourself. Just copy and paste into macOS project:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
// This line crashes the app! But w/o it the polygon plane would probably clip and be invisible.
cameraNode.camera?.zFar = 1000
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.intensity = 10000
ambientLightNode.light!.color = NSColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)
func model(vertices: [SCNVector3]) -> SCNNode {
let polyDraw = draw(vertices: vertices)
let material = SCNMaterial()
material.diffuse.contents = NSColor.green
material.isDoubleSided = true
material.diffuse.contentsTransform = .init(m11: 0.1, m12: 0, m13: 0, m14: 0,
m21: 0, m22: 0.1, m23: 0, m24: 0,
m31: 0, m32: 0, m33: 0, m34: 0,
m41: 0, m42: 0, m43: 0, m44: 1)
material.diffuse.wrapS = .repeat
material.diffuse.wrapT = .repeat
polyDraw.materials = [material]
let node = SCNNode(geometry: polyDraw)
node.scale = SCNVector3(x: 200, y: 200, z: 200)
scene.rootNode.addChildNode(node)
return node
}
func draw(vertices: [SCNVector3]) -> SCNGeometry {
let normalsPerFace = 1
var indices: [Int32] = []
indices.append(Int32(vertices.count))
//Add the rest of the indices 0 to vertices.count-1
for i in 0 ... vertices.count-1 {
indices.append(Int32(i))
}
let source = SCNGeometrySource(vertices: vertices)
let vec = vertices.map { [SCNVector3](repeating: $0,
count: normalsPerFace) }.flatMap{ $0 }
let normals: [SCNVector3] = vec
let normalSource = SCNGeometrySource(normals: normals)
var cgps: [CGPoint] = []
vertices.forEach { (vertex) in
cgps.append(CGPoint(x: CGFloat(vertex.x), y: CGFloat(vertex.y)))
}
let textcoord = SCNGeometrySource(textureCoordinates: cgps)
let data = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: data,
primitiveType: .polygon,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
return SCNGeometry(sources: [source, normalSource, textcoord], elements: [element])
}
//Previous fail, now success with ARGeo's code
// let vertices = [SCNVector3(x: 0.08866002, y: -0.00773552, z: -0.09841499),
// SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
// SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
// SCNVector3(x: 0.08846086, y: -0.02434851, z: -0.09852711),
// SCNVector3(x: 0.08749959, y: -0.03475155, z: -0.09906833),
// SCNVector3(x: 0.08527064, y: -0.04331201, z: -0.10032329),
// SCNVector3(x: 0.08125973, y: -0.04962304, z: -0.10258152),
// SCNVector3(x: 0.07674095, y: -0.05456349, z: -0.10512567),
// SCNVector3(x: 0.07041831, y: -0.05790819, z: -0.10868551),
// SCNVector3(x: 0.06373097, y: -0.05820452, z: -0.11245064),
// SCNVector3(x: 0.05844573, y: -0.05779012, z: -0.11542635),
// SCNVector3(x: 0.05448552, y: -0.05334358, z: -0.11765605),
// SCNVector3(x: 0.05290238, y: -0.04610482, z: -0.11854740),
// SCNVector3(x: 0.05353430, y: -0.03637475, z: -0.11819161),
// SCNVector3(x: 0.05589097, y: -0.02788102, z: -0.11686475),
// SCNVector3(x: 0.05910149, y: -0.02275178, z: -0.11505718),
// SCNVector3(x: 0.06234538, y: -0.02150976, z: -0.11323079),
// SCNVector3(x: 0.06506948, y: -0.02217681, z: -0.11169703)
// ]
//Array which fails when the cameras zFar property
//is too big (>10 or something).
//If zFar is small enough it does not crash,
//but then nothing is rendered.
let vertices = [ SCNVector3(x: 0.08291423, y: -0.08406013, z: -0.60201955),
SCNVector3(x: 0.077855, y: -0.083336316, z: -0.60234916),
SCNVector3(x: 0.077855, y: -0.083336316, z: -0.60234916),
SCNVector3(x: 0.06817482, y: -0.08799789, z: -0.6029798),
SCNVector3(x: 0.055873748, y: -0.09737456, z: -0.6037812),
SCNVector3(x: 0.042388167, y: -0.108595274, z: -0.60465986),
SCNVector3(x: 0.031522393, y: -0.119523935, z: -0.6053677),
SCNVector3(x: 0.024102041, y: -0.13026507, z: -0.6058511),
SCNVector3(x: 0.021609604, y: -0.13820335, z: -0.6060136),
SCNVector3(x: 0.022751667, y: -0.14294992, z: -0.60593915),
SCNVector3(x: 0.025871918, y: -0.14491153, z: -0.6057359),
SCNVector3(x: 0.0338943, y: -0.14688163, z: -0.6052132),
SCNVector3(x: 0.041132875, y: -0.15027393, z: -0.60474163),
SCNVector3(x: 0.047307685, y: -0.15410759, z: -0.6043393),
SCNVector3(x: 0.054541387, y: -0.1566292, z: -0.603868),
SCNVector3(x: 0.06140149, y: -0.15919833, z: -0.60342115),
SCNVector3(x: 0.06551884, y: -0.16264887, z: -0.6031529)
]
_ = model(vertices: vertices)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.darkGray
}
}
I have no idea what is causing this issue, as it seems to be some internal rendering going wrong. I'm also not very experienced with debugging so I would appreciate any help as I'm unable to make any further sense of this.
Solution
Copy-paste this macOS app code for testing in ViewController.swift:
import SceneKit
class ViewController: NSViewController, SCNSceneRendererDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.camera?.zFar = 1000
scene.rootNode.addChildNode(cameraNode)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
let scnView = self.view as! SCNView
scnView.scene = scene
scnView.delegate = self
scnView.allowsCameraControl = true
scnView.showsStatistics = true
scnView.backgroundColor = NSColor.darkGray
func model( v01: SCNVector3,
v02: SCNVector3,
v03: SCNVector3,
v04: SCNVector3,
v05: SCNVector3,
v06: SCNVector3,
v07: SCNVector3,
v08: SCNVector3,
v09: SCNVector3,
v10: SCNVector3,
v11: SCNVector3,
v12: SCNVector3,
v13: SCNVector3,
v14: SCNVector3,
v15: SCNVector3,
v16: SCNVector3,
v17: SCNVector3,
v18: SCNVector3) -> SCNNode {
let polyDraw = draw(vector01: v01,
vector02: v02,
vector03: v03,
vector04: v04,
vector05: v05,
vector06: v06,
vector07: v07,
vector08: v08,
vector09: v09,
vector10: v10,
vector11: v11,
vector12: v12,
vector13: v13,
vector14: v14,
vector15: v15,
vector16: v16,
vector17: v17,
vector18: v18)
let material = SCNMaterial()
material.diffuse.contents = NSColor.green
material.isDoubleSided = true
polyDraw.materials = [material]
let node = SCNNode(geometry: polyDraw)
node.scale = SCNVector3(x: 200, y: 200, z: 200)
scene.rootNode.addChildNode(node)
return node
}
func draw(vector01: SCNVector3,
vector02: SCNVector3,
vector03: SCNVector3,
vector04: SCNVector3,
vector05: SCNVector3,
vector06: SCNVector3,
vector07: SCNVector3,
vector08: SCNVector3,
vector09: SCNVector3,
vector10: SCNVector3,
vector11: SCNVector3,
vector12: SCNVector3,
vector13: SCNVector3,
vector14: SCNVector3,
vector15: SCNVector3,
vector16: SCNVector3,
vector17: SCNVector3,
vector18: SCNVector3) -> SCNGeometry {
let normalsPerFace = 1
let indices: [Int32] = [18, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17]
let source = SCNGeometrySource(vertices: [vector01,
vector02,
vector03,
vector04,
vector05,
vector06,
vector07,
vector08,
vector09,
vector10,
vector11,
vector12,
vector13,
vector14,
vector15,
vector16,
vector17,
vector18])
let vec = [vector01, vector02, vector03,
vector04, vector05, vector06,
vector07, vector08, vector09,
vector10, vector11, vector12,
vector13, vector14, vector15,
vector16, vector17, vector18]
.map { [SCNVector3](repeating: $0, count: normalsPerFace) }
.flatMap { $0 }
let normals: [SCNVector3] = vec
let normalSource = SCNGeometrySource(normals: normals)
let point01 = CGPoint(x: CGFloat(vector01.x), y: CGFloat(vector01.y))
let point02 = CGPoint(x: CGFloat(vector02.x), y: CGFloat(vector02.y))
let point03 = CGPoint(x: CGFloat(vector03.x), y: CGFloat(vector03.y))
let point04 = CGPoint(x: CGFloat(vector04.x), y: CGFloat(vector04.y))
let point05 = CGPoint(x: CGFloat(vector05.x), y: CGFloat(vector05.y))
let point06 = CGPoint(x: CGFloat(vector06.x), y: CGFloat(vector06.y))
let point07 = CGPoint(x: CGFloat(vector07.x), y: CGFloat(vector07.y))
let point08 = CGPoint(x: CGFloat(vector08.x), y: CGFloat(vector08.y))
let point09 = CGPoint(x: CGFloat(vector09.x), y: CGFloat(vector09.y))
let point10 = CGPoint(x: CGFloat(vector10.x), y: CGFloat(vector10.y))
let point11 = CGPoint(x: CGFloat(vector11.x), y: CGFloat(vector11.y))
let point12 = CGPoint(x: CGFloat(vector12.x), y: CGFloat(vector12.y))
let point13 = CGPoint(x: CGFloat(vector13.x), y: CGFloat(vector13.y))
let point14 = CGPoint(x: CGFloat(vector14.x), y: CGFloat(vector14.y))
let point15 = CGPoint(x: CGFloat(vector15.x), y: CGFloat(vector15.y))
let point16 = CGPoint(x: CGFloat(vector16.x), y: CGFloat(vector16.y))
let point17 = CGPoint(x: CGFloat(vector17.x), y: CGFloat(vector17.y))
let point18 = CGPoint(x: CGFloat(vector18.x), y: CGFloat(vector18.y))
let texCoord = SCNGeometrySource(textureCoordinates:
[point01, point02, point03, point04, point05, point06,
point07, point08, point09, point10, point11, point12,
point13, point14, point15, point16, point17, point18])
let data = Data(bytes: indices,
count: indices.count * MemoryLayout<Int32>.size)
let element = SCNGeometryElement(data: data,
primitiveType: .polygon,
primitiveCount: 1,
bytesPerIndex: MemoryLayout<Int32>.size)
let geometry = SCNGeometry(sources: [source, normalSource, texCoord],
elements: [element])
return geometry
}
_ = model(v01: SCNVector3(x: 0.08866002, y: -0.00773552, z: -0.09841499),
v02: SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
v03: SCNVector3(x: 0.08873053, y: -0.01492687, z: -0.09837532),
v04: SCNVector3(x: 0.08846086, y: -0.02434851, z: -0.09852711),
v05: SCNVector3(x: 0.08749959, y: -0.03475155, z: -0.09906833),
v06: SCNVector3(x: 0.08527064, y: -0.04331201, z: -0.10032329),
v07: SCNVector3(x: 0.08125973, y: -0.04962304, z: -0.10258152),
v08: SCNVector3(x: 0.07674095, y: -0.05456349, z: -0.10512567),
v09: SCNVector3(x: 0.07041831, y: -0.05790819, z: -0.10868551),
v10: SCNVector3(x: 0.06373097, y: -0.05820452, z: -0.11245064),
v11: SCNVector3(x: 0.05844573, y: -0.05779012, z: -0.11542635),
v12: SCNVector3(x: 0.05448552, y: -0.05334358, z: -0.11765605),
v13: SCNVector3(x: 0.05290238, y: -0.04610482, z: -0.11854740),
v14: SCNVector3(x: 0.05353430, y: -0.03637475, z: -0.11819161),
v15: SCNVector3(x: 0.05589097, y: -0.02788102, z: -0.11686475),
v16: SCNVector3(x: 0.05910149, y: -0.02275178, z: -0.11505718),
v17: SCNVector3(x: 0.06234538, y: -0.02150976, z: -0.11323079),
v18: SCNVector3(x: 0.06506948, y: -0.02217681, z: -0.11169703))
}
}
USEFUL INFO:
In 3D graphics the best and most predictable way to work with polygonal geometry is to initially use three-sided (triangles) and four-sided (quadrangles) faces. Sometimes, in rare cases, you can use five-sided faces but this can lead you to shading artefacts.
Bad cases that can potentially lead to errors in SceneKit/Metal are:
Lamina Faces
Non-Manifold Geometry
Non-planar Faces (your case)
Concave Faces
"Turned inside out" Faces i.e. wrong connection's order (your case)
Faces with Holes
Faces with Edges that have a zero length
etc...
And one more important thing I should say is: at rendering stage all polygons always turn into triangles. If renderer or rendering engine can't fulfil this transformation you'll get errors.
Look how four-sided polygons are competently connected to form a complex object:
P. S.
camera.zFar for ARKit.
let currentFrame = sceneView.session.currentFrame
let node = SCNNode()
node.camera = SCNCamera()
var translation = matrix_identity_float4x4
translation.columns.3.z = -0.1 /* 10 cm */
node.simdTransform = matrix_multiply((currentFrame?.camera.transform)!,
translation)
node.camera?.zFar = 1000 /* Set no more than 1000 meters */

How do I use an NSImage as material of an SCNGeometry shape correctly?

I got a task in the university to add a picture as texture to the SCNGeometry Octahedron. It's my first project in Swift.
There are lot's of advices for the UIKit with UIImage class, but I'm using AppKit for macos and NSImage class. And none of the options I found in the web haven't worked for me yet. Probably I misunderstand something fundamental.
Well, firstly I dragndroped a picture named "sims.jpg" to my project folder and to art.scnassets folder. And also added them with File → Add files to "art.scnassets" and general folder. And did nothing with Assets.xcassets.
Then here is how the shape is created:
func createOctahedron() {
let vertices: [SCNVector3] = [
SCNVector3(0, 1, 0),
SCNVector3(-0.5, 0, 0.5),
SCNVector3(0.5, 0, 0.5),
SCNVector3(0.5, 0, -0.5),
SCNVector3(-0.5, 0, -0.5),
SCNVector3(0, -1, 0)
]
let source = SCNGeometrySource(vertices: vertices)
let indices: [UInt16] = [
0, 1, 2,
2, 3, 0,
3, 4, 0,
4, 1, 0,
1, 5, 2,
2, 5, 3,
3, 5, 4,
4, 5, 1
]
let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let geometry = SCNGeometry(sources: [source], elements: [element])
let node = SCNNode(geometry: geometry)
node.geometry?.firstMaterial?.diffuse.contents = NSColor.green // назначаем цвет октаэдру
let scnView = self.view as! SCNView
scnView.scene?.rootNode.addChildNode(node)
let rotateAction = SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: .pi, z: 0, duration: 5))
node.runAction(rotateAction)
}
Just in case let me left a full code
So, I would add the image like this
let imageMaterial = SCNMaterial()
let image = NSImage.Name("sims")
imageMaterial.diffuse.contents = image
geometry.materials = [imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial]
Or maybe like that?
node.geometry?.firstMaterial?.diffuse.contents = NSImage.Name("sims")
Or should I additionally map it somehow? Help me please because I really do not get it. Xcode outputs just a rotating octahedron with no additional texture, no errors either
This line here:
let image = NSImage.Name("sims")
Only declares the name. You need:
let image = NSImage(named: NSImage.Name("sims"))
The reason your code compiles is that the contents property has a type of Any? so you can put any old goo in the property. It fails silently.
I found out what the deal was about. There was no texture appearing because of no UV mapping. I should have declared an array of 2d coordinates on the segment [0, 1] with the help of CGPoint and then add it to the source array.
Here is how it roughly looks like:
let width:CGFloat = 1/8
let coordinates: [CGPoint] = [
CGPoint(x: 0, y: 0),// BAC
CGPoint(x: width, y: 0),
CGPoint(x: width, y: 1),
CGPoint(x: 0, y: 1),// BAE
CGPoint(x: width * 7, y: 0),
CGPoint(x: 1, y: 0),
CGPoint(x: 1, y: 1),// EDA
CGPoint(x: width * 7, y: 1),
CGPoint(x: width * 6, y: 0),
CGPoint(x: width * 7, y: 0),// DAC
CGPoint(x: width, y: 0),
CGPoint(x: width * 4, y: 0),
CGPoint(x: width * 7, y: 1),// DFC
CGPoint(x: width * 6, y: 1),
CGPoint(x: width * 4, y: 1),
CGPoint(x: width, y: 1),// BFC
CGPoint(x: width * 4, y: 0),
CGPoint(x: width * 5, y: 0),
CGPoint(x: width * 5, y: 1),// BFE
CGPoint(x: width * 6, y: 1),
CGPoint(x: width * 5, y: 0),
CGPoint(x: width * 6, y: 0),// EFD
CGPoint(x: width * 4, y: 1),
CGPoint(x: width * 5, y: 1),
]
let source = [
SCNGeometrySource(vertices: vertices),
SCNGeometrySource(textureCoordinates: coordinates)
]
let element = SCNGeometryElement(indices: indices, primitiveType: .triangles)
let octahedron = SCNGeometry(sources: source, elements: [element])
After we have specified the coordinates it's enough to wright the following:
let image = NSImage(named: NSImage.Name("sims"))
octahedron.firstMaterial?.diffuse.contents = image
And the image will be stretched on faces of our octahedron.
The full code attaching

How to correctly render points in SceneKit with colours including alpha values?

I am trying to render a collection of points in SceneKit all of which have an alpha value less than 1 as part of their colour. While this seems to work, it depends on the angle at which you are looking at the point.
My current method has been to create an array of vertices with corresponding colours and indices, wrap these up inside SCNGeometrySource/SCNGeometryElement and create a custom SCNGeometry from theses. The SCNGeometryElement for the indices is set to render as points. Some sample code below of how I'm setting this up.
var vertices = [SCNVector3]()
var colors = [float4]()
var indices = [UInt32]()
for x in 0...10 {
for y in 0...10 {
for z in 0...10 {
vertices.append(SCNVector3(x - 5, y - 5, z - 5))
colors.append(float4(0.7, 0.7, 0.7, 0.1))
indices.append(UInt32(vertices.count - 1))
}
}
}
let vertexSource = SCNGeometrySource(vertices: vertices)
let colorsData = Data(bytes: &colors, count: colors.count * MemoryLayout<float4>.stride)
let colorSource = SCNGeometrySource(data: colorsData, semantic: .color, vectorCount: colors.count, usesFloatComponents: true, componentsPerVector: 4, bytesPerComponent: MemoryLayout<Float>.stride, dataOffset: 0, dataStride: MemoryLayout<float4>.stride)
let pointsElement = SCNGeometryElement(indices: indices, primitiveType: .point)
pointsElement.pointSize = 0.2
pointsElement.minimumPointScreenSpaceRadius = 1
pointsElement.maximumPointScreenSpaceRadius = 50
let geometry = SCNGeometry(sources: [vertexSource, colorSource], elements: [pointsElement])
let node = SCNNode(geometry: geometry)
scene.rootNode.addChildNode(node)
When my camera node is located as follow, everything looks good:
cameraNode.eulerAngles = SCNVector3(0, 0.5 * CGFloat.pi, 0)
cameraNode.position = SCNVector3(x: 10, y: 0, z: 0)
However if I move my camera to the next face of the cube clockwise the alpha no longer renders:
cameraNode.eulerAngles = SCNVector3(0, -0.5 * CGFloat.pi, 0)
cameraNode.position = SCNVector3(x: -10, y: 0, z: 0)
Placing the camera half way gives a combination of the two:
cameraNode.eulerAngles = SCNVector3(0, 0, 0)
cameraNode.position = SCNVector3(x: 0, y: 0, z: 10)